RabbitMQ保姆級教程最佳實踐

一、消息隊列介紹

1、消息隊列概念

1、MQ全稱爲Message Queue,消息隊列(MQ)是⼀種應⽤程序對應⽤程序的通信⽅法。
應⽤程序通過讀寫出⼊隊列的消息(針對應⽤程序的數據)來通信,⽽⽆需專⽤連接來
鏈接它們。
2、消息傳遞指的是程序之間通過在消息中發送數據進⾏通信,⽽不是通過直接調⽤彼此來
通信,直接調⽤通常是⽤於諸如遠程過程調⽤的技術。

2、常⽤的消息隊列產品

1、RabbitMQ 穩定可靠,數據⼀致,⽀持多協議,有消息確認,基於erlang語⾔
2、Kafka ⾼吞吐,⾼性能,快速持久化,⽆消息確認,⽆消息遺漏,可能會有有重複消息,依賴於zookeeper,成本⾼.
3、ActiveMQ 不夠靈活輕巧,對隊列較多情況⽀持不好.
4、RocketMQ 性能好,⾼吞吐,⾼可⽤性,⽀持⼤規模分佈式,協議⽀持單⼀

⼆、RabbitMQ

1、RabbitMQ介紹

1、RabbitMQ是⼀個在AMQP基礎上完成的,可復⽤的企業消息系統。他遵循MozillaPublic License開源協議。
2、AMQP,即Advanced Message Queuing Protocol, ⼀個提供統⼀消息服務的應⽤層標準
     ⾼級消息隊列協議,是應⽤層協議的⼀個開放標準,爲⾯向消息的中間件設計。基於此協議
     的客戶端與消息中間件可傳遞消息,並不受客戶端/中間件不同產品,不同的開發語⾔等
     條件的限制。Erlang中的實現有 RabbitMQ等。
3、主要特性:
  • 保證可靠性 :使⽤⼀些機制來保證可靠性,如持久化、傳輸確認、發佈確認
  • 靈活的路由功能
  • 可伸縮性:⽀持消息集羣,多臺RabbitMQ服務器可以組成⼀個集羣
  • ⾼可⽤性 :RabbitMQ集羣中的某個節點出現問題時隊列仍然可⽤
  • ⽀持多種協議
  • ⽀持多語⾔客戶端
  • 提供良好的管理界⾯
  • 提供跟蹤機制:如果消息出現異常,可以通過跟蹤機制分析異常原因
  • 提供插件機制:可通過插件進⾏多⽅⾯擴展

2、RabbitMQ安裝和配置

具體參考:https://note.youdao.com/s/MKn2Jr8c

3、RabbitMQ邏輯結構

三、RabbitMQ⽤戶管理

RabbitMQ默認提供了⼀個guests賬號,但是此賬號不能⽤作遠程登錄,也就是不能在管理系統的登錄;我們可以創建⼀個新的賬號並授予響應的管理權限來實現遠程登錄

1、邏輯結構

⽤戶
虛擬主機
隊列

2、⽤戶管理

2.1、命令⾏⽤戶管理

1、在linux中使⽤命令⾏創建⽤戶

## 進⼊到rabbit_mq的sbin⽬錄
cd /usr/local/rabbitmq_server-3.7.0/sbin
## 新增⽤戶
./rabbitmqctl add_user ytao admin123

2、設置⽤戶級別

## ⽤戶級別:
## 1.administrator 可以登錄控制檯、查看所有信息、可以對RabbitMQ進⾏管理
## 2.monitoring 監控者 登錄控制檯、查看所有信息
## 3.policymaker 策略制定者 登錄控制檯、指定策略
## 4.managment 普通管理員 登錄控制檯
./rabbitmqctl set_user_tags ytao administrator

2.2、管理系統進⾏⽤戶管理

管理系統登錄:訪問http://localhost:15672/

四、RabbitMQ⼯作⽅式

RabbitMQ提供了多種消息的通信⽅式—⼯作模式  https://www.rabbitmq.com/getstarted.html
消息通信是由兩個⻆⾊完成:消息⽣產者(producer)和 消息消費者(Consumer)

1、簡單模式

⼀個隊列只有⼀個消費者

2、⼯作模式

多個消費者監聽同⼀個隊列

3、訂閱模式

⼀個交換機綁定多個消息隊列,每個消息隊列有⼀個消費者監聽

4、路由模式

⼀個交換機綁定多個消息隊列,每個消息隊列都由⾃⼰唯⼀的key,每個消息隊列有⼀個消費者監聽

五、RabbitMQ交換機和隊列管理

1、創建隊列

2、創建交換機

3、交換機綁定隊列

六、在普通的Maven應⽤中使⽤MQ

1、簡單模式

1.1、消息⽣產者

1、創建Maven項⽬

2、添加RabbitMQ連接所需要的依賴

<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
<dependency>
 <groupId>com.rabbitmq</groupId>
 <artifactId>amqp-client</artifactId>
 <version>4.10.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
 <groupId>org.slf4j</groupId>
 <artifactId>slf4j-log4j12</artifactId>
 <version>1.7.25</version>
 <scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commonslang3 -->
<dependency>
 <groupId>org.apache.commons</groupId>
 <artifactId>commons-lang3</artifactId>
 <version>3.9</version>
</dependency>

3、在resources⽬錄下創建log4j.properties

log4j.rootLogger=DEBUG,A1 log4j.logger.com.taotao = DEBUG
log4j.logger.org.mybatis = DEBUG
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n

4、創建MQ連接工具類

import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConnectionUtil {
    public static Connection getConnection() throws IOException,
    TimeoutException {
        //1.創建連接⼯⼚
        ConnectionFactory factory = new ConnectionFactory();
        //2.在⼯⼚對象中設置MQ的連接信息
        (ip,port,virtualhost,username,password)
         factory.setHost("47.96.11.185");
        factory.setPort(5672);
        factory.setVirtualHost("host1");
        factory.setUsername("ytao");
        factory.setPassword("admin123");
        //3.通過⼯⼚對象獲取與MQ的鏈接
        Connection connection = factory.newConnection();
        return connection;
    }
}

5、消息⽣產者發送消息

import com.qfedu.mq.utils.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
public class SendMsg {
    public static void main(String[] args) throws Exception{
        String msg = "Hello HuangDaoJun!";
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        //定義隊列(使⽤Java代碼在MQ中新建⼀個隊列)
        //參數1:定義的隊列名稱
        //參數2:隊列中的數據是否持久化(如果選擇了持久化)
        //參數3: 是否排外(當前隊列是否爲當前連接私有)
        //參數4:⾃動刪除(當此隊列的連接數爲0時,此隊列會銷燬(⽆論隊列中是否還有數據))
        //參數5:設置當前隊列的參數
        //channel.queueDeclare("queue7",false,false,false,null);
        //參數1:交換機名稱,如果直接發送信息到隊列,則交換機名稱爲""
        //參數2:⽬標隊列名稱
        //參數3:設置當前這條消息的屬性(設置過期時間 10)
        //參數4:消息的內容
        channel.basicPublish("","queue1",null,msg.getBytes());
        System.out.println("發送:" + msg);
        channel.close();
        connection.close();
    }
}

1.2、消息消費者

1、創建Maven項⽬
2、添加依賴
3、log4j.properties
4、ConnetionUtil.java
5、消費者消費消息
import com.qfedu.mq.utils.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ReceiveMsg {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //body就是從隊列中獲取的數據
                String msg = new String(body);
                System.out.println("接收:"+msg);
            }
        };
        channel.basicConsume("queue1",true,consumer);
    }
}

2、⼯作模式

⼀個發送者多個消費者

2.1、發送者

public class SendMsg {
    public static void main(String[] args) throws Exception{
        System.out.println("請輸⼊消息:");
        Scanner scanner = new Scanner(System.in);
        String msg = null;
        while(!"quit".equals(msg = scanner.nextLine())){
            Connection connection = ConnectionUtil.getConnection();
            Channel channel = connection.createChannel();
            channel.basicPublish("","queue2",null,msg.getBytes());
            System.out.println("發送:" + msg);
            channel.close();
            connection.close();
        }
    }
}

2.2、消費者1

public class ReceiveMsg {
    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //body就是從隊列中獲取的數據
                String msg = new String(body);
                System.out.println("Consumer1接收:"+msg);
                if("wait".equals(msg)){
                    try {
                        Thread.sleep(10000);
                    }
                    catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        channel.basicConsume("queue2",true,consumer);
    }
}

2.3、消費者2

public class ReceiveMsg {
    public static void main(String[] args) throws IOException,
    TimeoutException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //body就是從隊列中獲取的數據
                String msg = new String(body);
                System.out.println("Consumer2接收:"+msg);
            }
        };
        channel.basicConsume("queue2",true,consumer);
    }
}

3、訂閱模式

1、發送者 發送消息到交換機

public class SendMsg {
    public static void main(String[] args) throws Exception{
        System.out.println("請輸⼊消息:");
        Scanner scanner = new Scanner(System.in);
        String msg = null;
        while(!"quit".equals(msg = scanner.nextLine())){
            Connection connection = ConnectionUtil.getConnection();
            Channel channel = connection.createChannel();
            channel.basicPublish("ex1","",null,msg.getBytes());
            System.out.println("發送:" + msg);
            channel.close();
            connection.close();
        }
    }
}

2、消費者1

public class ReceiveMsg1 {
    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //body就是從隊列中獲取的數據
                String msg = new String(body);
                System.out.println("Consumer1接收:"+msg);
                if("wait".equals(msg)){
                    try {
                        Thread.sleep(10000);
                    }
                    catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        channel.basicConsume("queue3",true,consumer);
    }
}

3、消費者2

public class ReceiveMsg2 {
    public static void main(String[] args) throws IOException,
    TimeoutException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //body就是從隊列中獲取的數據
                String msg = new String(body);
                System.out.println("Consumer2接收:"+msg);
            }
        }
        ;
        channel.basicConsume("queue4",true,consumer);
    }
}

4、路由模式

1、發送者 發送消息到交換機

public class SendMsg {
    public static void main(String[] args) throws Exception{
        System.out.println("請輸⼊消息:");
        Scanner scanner = new Scanner(System.in);
        String msg = null;
        while(!"quit".equals(msg = scanner.nextLine())){
            Connection connection = ConnectionUtil.getConnection();
            Channel channel = connection.createChannel();
            if(msg.startsWith("a")){
                channel.basicPublish("ex2","a",null,msg.getBytes());
            } else if(msg.startsWith("b")){
                channel.basicPublish("ex2","b",null,msg.getBytes());
            }
            System.out.println("發送:" + msg);
            channel.close();
            connection.close();
        }
    }
}

2、消費者1

public class ReceiveMsg1 {
    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //body就是從隊列中獲取的數據
                String msg = new String(body);
                System.out.println("Consumer1接收:"+msg);
                if("wait".equals(msg)){
                    try {
                        Thread.sleep(10000);
                    }
                    catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        channel.basicConsume("queue5",true,consumer);
    }
}

3、消費者2

public class ReceiveMsg2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //body就是從隊列中獲取的數據
                String msg = new String(body);
                System.out.println("Consumer2接收:"+msg);
            }
        };
        channel.basicConsume("queue6",true,consumer);
    }
}

七、在SpringBoot應⽤中使⽤MQ

SpringBoot應⽤可以完成⾃動配置及依賴注⼊——可以通過Spring直接提供與MQ的連接對象

1、消息⽣產者

1、創建SpringBoot應⽤,添加依賴

2、配置application.yml

server:
  port: 9001
spring:
  application:
    name: producer
  rabbitmq:
    host: 47.96.11.185
    port: 5672
    virtual-host: host1
    username: ytao
    password: admin123

3、發送消息

@Service
public class TestService {
    @Resource
    private AmqpTemplate amqpTemplate;
    public void sendMsg(String msg){
        //1. 發送消息到隊列
        amqpTemplate.convertAndSend("queue1",msg);
        //2. 發送消息到交換機(訂閱交換機)
        amqpTemplate.convertAndSend("ex1","",msg);
        //3. 發送消息到交換機(路由交換機)
        amqpTemplate.convertAndSend("ex2","a",msg);
    }
}

2、消息消費者

1、創建項⽬添加依賴
2、配置yml
3、接收消息
@Service
//@RabbitListener(queues = {"queue1","queue2"})
@RabbitListener(queues = "queue1")
public class ReceiveMsgService {
    @RabbitHandler
    public void receiveMsg(String msg){
        System.out.println("接收MSG:"+msg);
    }
}

⼋、使⽤RabbitMQ傳遞對象

RabbitMQ是消息隊列,發送和接收的都是字符串/字節數組類型的消息

1、使⽤序列化對象

要求:
傳遞的對象實現序列化接⼝
傳遞的對象的包名、類名、屬性名必須⼀致

 

1、消息提供者

@Service
public class MQService {
    @Resource
    private AmqpTemplate amqpTemplate;
    public void sendGoodsToMq(Goods goods){
        //消息隊列可以發送 字符串、字節數組、序列化對象
        amqpTemplate.convertAndSend("","queue1",goods);
    }
}

2、消息消費者

@Component
@RabbitListener(queues = "queue1")
public class ReceiveService {
    @RabbitHandler
    public void receiveMsg(Goods goods){
        System.out.println("Goods---"+goods);
    }
}

2、使⽤序列化字節數組

要求:
  傳遞的對象實現序列化接⼝
  傳遞的對象的包名、類名、屬性名必須⼀致

 

1、消息提供者

@Service
public class MQService {
    @Resource
    private AmqpTemplate amqpTemplate;
    public void sendGoodsToMq(Goods goods){
        //消息隊列可以發送 字符串、字節數組、序列化對象
        byte[] bytes = SerializationUtils.serialize(goods);
        amqpTemplate.convertAndSend("","queue1",bytes);
    }
}

2、消息消費者

@Component
@RabbitListener(queues = "queue1")
public class ReceiveService {
    @RabbitHandler
    public void receiveMsg(byte[] bs){
        Goods goods = (Goods) SerializationUtils.deserialize(bs);
        System.out.println("byte[]---"+goods);
    }
}

3、使⽤JSON字符串傳遞

要求:對象的屬性名⼀直

 

1、消息提供者

@Service
public class MQService {
    @Resource
    private AmqpTemplate amqpTemplate;
    public void sendGoodsToMq(Goods goods) throws JsonProcessingException {
        //消息隊列可以發送 字符串、字節數組、序列化對象
        ObjectMapper objectMapper = new ObjectMapper();
        String msg = objectMapper.writeValueAsString(goods);
        amqpTemplate.convertAndSend("","queue1",msg);
    }
}

2、消息消費者

@Component
@RabbitListener(queues = "queue1")
public class ReceiveService {
    @RabbitHandler
    public void receiveMsg(String msg) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        Goods goods = objectMapper.readValue(msg,Goods.class);
        System.out.println("String---"+msg);
    }
}

九、基於Java的交換機與隊列創建

我們使⽤消息隊列,消息隊列和交換機可以通過管理系統完成創建,也可以在應⽤程序中通過Java代碼來完成創建

1、普通Maven項⽬交換機及隊列創建

1、使⽤Java代碼新建隊列

//1.定義隊列 (使⽤Java代碼在MQ中新建⼀個隊列)
//參數1:定義的隊列名稱
//參數2:隊列中的數據是否持久化(如果選擇了持久化)
//參數3: 是否排外(當前隊列是否爲當前連接私有)
//參數4:⾃動刪除(當此隊列的連接數爲0時,此隊列會銷燬(⽆論隊列中是否還有數據))
//參數5:設置當前隊列的參數
channel.queueDeclare("queue7",false,false,false,null);

2、新建交換機

//定義⼀個“訂閱交換機”
channel.exchangeDeclare("ex3", BuiltinExchangeType.FANOUT);
//定義⼀個“路由交換機”
channel.exchangeDeclare("ex4", BuiltinExchangeType.DIRECT);

3、綁定隊列到交換機

//綁定隊列
//參數1:隊列名稱
//參數2:⽬標交換機
//參數3:如果綁定訂閱交換機參數爲"",如果綁定路由交換機則表示設置隊列的key
channel.queueBind("queue7","ex4","k1");
channel.queueBind("queue8","ex4","k2");

2、SpringBoot應⽤中通過配置完成隊列的創建

@Configuration
public class RabbitMQConfiguration {
    //聲明隊列
    @Bean
    public Queue queue9(){
        Queue queue9 = new Queue("queue9");
        //設置隊列屬性
        return queue9;
    }
    @Bean
    public Queue queue10(){
        Queue queue10 = new Queue("queue10");
        //設置隊列屬性
        return queue10;
    }
    //聲明訂閱模式交換機
    @Bean
    public FanoutExchange ex5(){
        return new FanoutExchange("ex5");
    }
    //聲明路由模式交換機
    @Bean
    public DirectExchange ex6(){
        return new DirectExchange("ex6");
    }
    //綁定隊列
    @Bean
    public Binding bindingQueue9(Queue queue9, DirectExchange ex6){
        return BindingBuilder.bind(queue9).to(ex6).with("k1");
    }
    @Bean
    public Binding bindingQueue10(Queue queue10, DirectExchange ex6){
        return BindingBuilder.bind(queue10).to(ex6).with("k2");
    }
}

⼗、消息的可靠性

消息的可靠性:從 ⽣產者發送消息 —— 消息隊列存儲消息 —— 消費者消費消息 的整個過程中消息的安全性及可控性。
  • ⽣產者
  • 消息隊列
  • 消費者

1、RabbitMQ事務

RabbitMQ事務指的是基於客戶端實現的事務管理,當在消息發送過程中添加了事務,處理效率降低⼏⼗倍甚⾄上百倍 
Connection connection = RabbitMQUtil.getConnection(); //connection 表示與 host1的連接
Channel channel = connection.createChannel();
channel.txSelect();//開啓事務
try{
    channel.basicPublish("ex4", "k1", null, msg.getBytes());
    channel.txCommit();//提交事務
}
catch (Exception e){
    channel.txRollback();//事務回滾
}
finally{
    channel.close();
    connection.close();
}

2、RabbitMQ消息確認和return機制

1、消息確認機制:確認消息提供者是否成功發送消息到交換機
2、return機制:確認消息是否成功的從交換機分發到隊列

2.1、普通Maven項⽬的消息確認

1、普通confirm⽅式

//1.發送消息之前開啓消息確認
channel.confirmSelect();
channel.basicPublish("ex1", "a", null, msg.getBytes());
//2.接收消息確認
Boolean b = channel.waitForConfirms();
System.out.println("發送:" +(b?"成功":"失敗"));

2、批量confirm⽅式

//1.發送消息之前開啓消息確認
channel.confirmSelect();
//2.批量發送消息
for (int i=0 ; i<10 ; i++){
    channel.basicPublish("ex1", "a", null, msg.getBytes());
}
//3.接收批量消息確認:發送的所有消息中,如果有⼀條是失敗的,則所有消息發送直接失敗,拋出IO異常
Boolean b = channel.waitForConfirms();

3、異步confirm⽅式

//發送消息之前開啓消息確認
channel.confirmSelect();
//批量發送消息
for (int i=0 ; i<10 ; i++){
    channel.basicPublish("ex1", "a", null, msg.getBytes());
}
//假如發送消息需要10s,waitForConfirms會進⼊阻塞狀態
//boolean b = channel.waitForConfirms();
//使⽤監聽器異步confirm
channel.addConfirmListener(new ConfirmListener() {
    //參數1: long l 返回消息的表示
    //參數2: boolean b 是否爲批量confirm
    public void handleAck(long l, Boolean b) throws IOException {
        System.out.println("~~~~~消息成功發送到交換機");
    }
    public void handleNack(long l, Boolean b) throws IOException {
        System.out.println("~~~~~消息發送到交換機失敗");
    }
}
);

2.2、普通Maven項⽬的return機制

1、添加return監聽器
2、發送消息是指定第三個參數爲true
3、由於監聽器監聽是異步處理,所以在消息發送之後不能關閉channel
String msg = "Hello HuangDaoJun!";
Connection connection = ConnectionUtil.getConnection();
//相當於JDBC操作的數據庫連接
Channel channel = connection.createChannel();
//相當於JDBC操作的statement
//return機制:監控交換機是否將消息分發到隊列
channel.addReturnListener(new ReturnListener() {
    public void handleReturn(int i, String s, String s1, String s2,AMQP.BasicProperties basicProperties,byte[] bytes) throws IOException {
        //如果交換機分發消息到隊列失敗,則會執⾏此⽅法(⽤來處理交換機分發消息到隊列失敗的情況)
        System.out.println("*****"+i);//標識
        System.out.println("*****"+s);//
        System.out.println("*****"+s1);//交換機名
        System.out.println("*****"+s2);//交換機對應的隊列的key
        System.out.println("*****"+new String(bytes));//發送的消息
    }
}
);
//發送消息
//channel.basicPublish("ex2", "c", null, msg.getBytes());
channel.basicPublish("ex2", "c", true, null, msg.getBytes());

2.3、在SpringBoot應⽤實現消息確認與return監聽

1、配置application.yml,開啓消息確認和return監聽

spring:
 rabbitmq:
    publisher-confirm-type: simple ## 開啓消息確認模式
    publisher-returns: true ##使⽤return監聽機制

2、創建confirm和return監聽

2.1、消息確認

@Component
public class MyConfirmListener implements
RabbitTemplate.ConfirmCallback {
    @Autowired
    private AmqpTemplate amqpTemplate;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @PostConstruct
    public void init(){
        rabbitTemplate.setConfirmCallback(this);
    }
    @Override
    public void confirm(CorrelationData correlationData, Boolean b, String s) {
        //參數b 表示消息確認結果
        //參數s 表示發送的消息
        if(b){
            System.out.println("消息發送到交換機成功!");
        } else{
            System.out.println("消息發送到交換機失敗!");
            amqpTemplate.convertAndSend("ex4","",s);
        }
    }
}

2.2、return機制

@Component
public class MyReturnListener implements RabbitTemplate.ReturnsCallback
{
    @Autowired
    private AmqpTemplate amqpTemplate;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @PostConstruct
    public void init(){
        rabbitTemplate.setReturnsCallback(this);
    }
    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        System.out.println("消息從交換機分發到隊列失敗");
        String exchange = returnedMessage.getExchange();
        String routingKey = returnedMessage.getRoutingKey();
        String msg = returnedMessage.getMessage().toString();
        amqpTemplate.convertAndSend(exchange,routingKey,msg);
    }
}

3、RabbitMQ消費者⼿動應答 

@Component
@RabbitListener(queues="queue01")
public class Consumer1 {
    @RabbitHandler
    public void process(String msg,Channel channel, Message message) throws IOException {
        try {
            System.out.println("get msg1 success msg = "+msg);
            /**
         * 確認⼀條消息:<br>
         * channel.basicAck(deliveryTag, false); <br>
         * deliveryTag:該消息的index <br>
         * multiple:是否批量.true:將⼀次性ack所有⼩於deliveryTag的消息 <br>
       */
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            //消費者處理出了問題,需要告訴隊列信息消費失敗
            /**
         * 拒絕確認消息:<br>
         * channel.basicNack(long deliveryTag, boolean multiple, boolean requeue) ; <br>
         * deliveryTag:該消息的index<br>
         * multiple:是否批量.true:將⼀次性拒絕所有⼩於deliveryTag的消息。<br>
         * requeue:被拒絕的是否重新⼊隊列 <br>
       */
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            System.err.println("get msg1 failed msg = "+msg);
        }
    }
}

4、消息消費的冪等性問題

消息消費的冪等性——多次消費的執⾏結果時相同的 (避免重複消費)
解決⽅案:處理成功的消息setnx到redis

⼗⼀、延遲機制

1、延遲隊列

1、延遲隊列——消息進⼊到隊列之後,延遲指定的時間才能被消費者消費
2、AMQP協議和RabbitMQ隊列本身是不⽀持延遲隊列功能的,但是可以通過TTL(Time To Live)特性模擬延遲隊列的功能
3、TTL就是消息的存活時間。RabbitMQ可以分別對隊列和消息設置存活時間

1、在創建隊列的時候可以設置隊列的存活時間,當消息進⼊到隊列並且在存活時間內沒有消費者消費,則此消息就會從當前隊列被移除;
2、創建消息隊列沒有設置TTL,但是消息設置了TTL,那麼當消息的存活時間結束,也會被移除;
3、當TTL結束之後,我們可以指定將當前隊列的消息轉存到其他指定的隊列

2、使⽤延遲隊列實現訂單⽀付監控

1、實現流程圖

 2、創建交換機和隊列

⼗⼆、消息隊列作⽤/使⽤場景總結

1、解耦

場景說明:⽤戶下單之後,訂單系統要通知庫存系統

2、異步

場景說明:⽤戶註冊成功之後,需要發送註冊郵件及註冊短信提醒

3、消息通信

場景說明:應⽤系統之間的通信,例如聊天室

4、流量削峯

場景說明:秒殺業務

5、⽇志處理

場景說明:系統中⼤量的⽇志處理

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章