RabbitMQ保姆級教程最佳實踐

佛祖讓我來巡山發表於2023-09-23

一、訊息佇列介紹

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、⽇志處理

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

 

相關文章