RabbitMQ入門到精通_餘勝軍版筆記

原筆記鏈接:https://files.cnblogs.com/files/henuliulei/Rabbitmq%E5%85%A5%E9%97%A8%E5%88%B0%E7%B2%BE%E9%80%9A.zip

原視頻鏈接:2021年RabbitMQ入門到精通 餘勝軍(通俗易懂版本)_嗶哩嗶哩_bilibili

MQ架構設計原理

什麼是消息中間件

消息中間件基於隊列模型實現異步/同步傳輸數據

作用:可以實現支撐高併發、異步解耦、流量削峯、降低耦合度。

在瞭解中間件之前,我們先了解一下什麼是同步?

首先我們想一下,兩個公司之間如果有互相調用接口的業務需求,如果沒有引入中間件技術,是怎麼實現的呢?

用戶發起請求給系統A,系統A接到請求直接調用系統B,系統B返回結果後,系統A才能返回結果給用戶,這種模式就是同步調用。

 

所謂同步調用就是各個系統之間互相依賴,一個系統發送請求,其他系統也會跟着依次進行處理,只有所有系統處理完成後對於用戶來講纔算完成了一次請求。只要其他系統出現故障,就會報錯給用戶。

 

那麼引入中間件後,是如何做到異步調用的呢?

用戶發起請求給系統A,此時系統A發送消息給MQ,然後就返回結果給用戶,不去管系統B了。然後系統B根據自己的情況,去MQ中獲取消息,獲取到消息的時候可能已經過了1分鐘甚至1小時,再根據消息的指示執行相應的操作。

那麼想一想,系統A和系統B互相之間是否有通信?這種調用方式是同步調用嗎?

系統A發送消息給中間件後,自己的工作已經完成了,不用再去管系統B什麼時候完成操作。而系統B拉去消息後,執行自己的操作也不用告訴系統A執行結果,所以整個的通信過程是異步調用的。

 

說到這裏,我們可以做個總結,消息中間件到底是什麼呢?

其實消息中間件就是一個獨立部署的系統。可以實現各個系統之間的異步調用。當然它的作用可不止這些,通過它可以解決大量的技術痛點,我們接下來會進行介紹。

 

消息中間件,總結起來作用有三個:異步化提升性能、降低耦合度、流量削峯。

 

異步化提升性能

先來說說異步化提升性能,上邊我們介紹中間件的時候已經解釋了引入中間件後,是如何實現異步化的,但沒有解釋具體性能是怎麼提升的,我們來看一下下邊的圖。

 

 沒有引入中間件的時候,用戶發起請求到系統A,系統A耗時20ms,接下來系統A調用系統B,系統B耗時200ms,帶給用戶的體驗就是,一個操作全部結束一共耗時220ms。

如果引入中間件之後呢?看下邊的圖。

 

 用戶發起請求到系統A,系統A耗時20ms,發送消息到MQ耗時5ms,返回結果一共用了25ms,用戶體驗一個操作只用了25ms,而不用管系統B什麼時候去獲取消息執行對應操作,這樣比較下來,性能自然大大提高

 

降低耦合度

再來聊聊解耦的場景,看下圖。

 

如果沒有引入中間件,那麼系統A調用系統B的時候,系統B出現故障,導致調用失敗,那麼系統A就會接到異常信息,接到異常信息後肯定要再處理一下,返回給用戶失敗請稍後再試,這時候就得等待系統B的工程師解決問題,一切都解決好後再告知用戶可以了,再重新操作一次吧。

這樣的架構,兩個系統耦合再一起,用戶體驗極差。

那麼我們引入中間件後是什麼樣的場景呢,看下面的流程:

 對於系統A,發送消息後直接返回結果,不再管系統B後邊怎麼操作。而系統B故障恢復後重新到MQ中拉取消息,重新執行未完成的操作,這樣一個流程,系統之間沒有影響,也就實現瞭解耦。

 

流量削峯

下面我們再聊聊最後一個場景,流量削峯

 

假如我們的系統A是一個集羣,不連接數據庫,這個集羣本身可以抗下1萬QPS

系統B操作的是數據庫,這個數據庫只能抗下6000QPS,這就導致無論系統B如何擴容集羣,都只能抗下6000QPS,它的瓶頸在於數據庫。

假如突然系統QPS達到1萬,就會直接導致數據庫崩潰,那麼引入MQ後是怎麼解決的呢,見下圖:

 

 引入MQ後,對於系統A沒有什麼影響,給MQ發送消息可以直接發送1萬QPS。

此時對於系統B,可以自己控制獲取消息的速度,保持在6000QPS一下,以一個數據庫能夠承受的速度執行操作。這樣就可以保證數據庫不會被壓垮。

當然,這種情況MQ中可能會積壓大量消息。但對於MQ來說,是允許消息積壓的,等到系統A峯值過去,恢復成1000QPS時,系統B還是在以6000QPS的速度去拉取消息,自然MQ中的消息就慢慢被釋放掉了。

這就是流量削峯的過程。在電商秒殺、搶票等等具有流量峯值的場景下可以使用這麼一套架構。

傳統的http請求存在那些缺點

1.Http請求基於請求與響應的模型,在高併發的情況下,客戶端發送大量的請求達到

服務器端有可能會導致我們服務器端處理請求堆積。

2.Tomcat服務器處理每個請求都有自己獨立的線程,如果超過最大線程數會將該請求緩存到隊列中,如果請求堆積過多的情況下,有可能會導致tomcat服務器崩潰的問題。

所以一般都會在nginx入口實現限流,整合服務保護框架。

 

 

 

 

http請求處理業務邏輯如果比較耗時的情況下,容易造成客戶端一直等待,阻塞等待 過程中會導致客戶端超時發生重試策略,有可能會引發冪等性問題。

注意事項:接口是爲http協議的情況下,最好不要處理比較耗時的業務邏輯,耗時的業務邏輯應該單獨交給多線程或者是mq處理。

 

Mq應用場景有那些

  1. 異步發送短信
  2. 異步發送新人優惠券
  3. 處理一些比較耗時的操作

爲什麼需要使用mq

可以實現支撐高併發、異步解耦、流量削峯、降低耦合度。

同步發送http請求

 

 

 

客戶端發送請求到達服務器端,服務器端實現會員註冊業務邏輯,

1.insertMember() --插入會員數據  1s

2.sendSms()----發送登陸短信提醒 3s

3.sendCoupons()----發送新人優惠券  3s

總共響應需要6s時間,可能會導致客戶端阻塞6s時間,對用戶體驗

不是很好。

多線程與MQ方式實現異步?

互聯網項目:

客戶端 安卓/IOS

服務器端:php/java

最好使用mq實現異步

 

多線程處理業務邏輯

 

 

 

 

用戶向數據庫中插入一條數據之後,在單獨開啓一個線程異步發送短信和優惠操作。

客戶端只需要等待1s時間

優點:適合於小項目 實現異步

缺點:有可能會消耗服務器cpu資源資源

Mq處理業務邏輯

 

 

 

先向數據庫中插入一條會員數據,讓後再向MQ中投遞一個消息,MQ服務器端在將消息推送給消費者異步解耦處理髮送短信和優惠券。

Mq與多線程之間區別

MQ可以實現異步/解耦/流量削峯問題;

多線程也可以實現異步,但是消耗到cpu資源,沒有實現解耦。

Mq消息中間件名詞

Producer 生產者:投遞消息到MQ服務器端;

Consumer  消費者:從MQ服務器端獲取消息處理業務邏輯;

Broker   MQ服務器端

Topic 主題:分類業務邏輯發送短信主題、發送優惠券主題

Queue 存放消息模型 隊列 先進先出 後進後出原則 數組/鏈表

Message 生產者投遞消息報文:json

主流mq區別對比

 簡單的比較

特性

ActiveMQ

RabbitMQ

RocketMQ

kafka

開發語言

java

erlang

java

scala

單機吞吐量

萬級

萬級

10萬級

10萬級

時效性

ms級

us級

ms級

ms級以內

可用性

高(主從架構)

高(主從架構)

非常高(分佈式架構)

非常高(分佈式架構)

功能特性

成熟的產品,在很多公司得到應用;有較多的文檔;各種協議支持較好

基於erlang開發,所以併發能力很強,性能極其好,延時很低管理界面較豐富

MQ功能比較完備,擴展性佳

只支持主要的MQ功能,像一些消息查詢,消息回溯等功能沒有提供,畢竟是爲大數據準備的,在大數據領域應用廣。

 https://copyfuture.com/blogs-details/20190816154718064u4lgjgg20z1o6id

 

 

 

 

 

 

 

 

 

 

 

 

 

Mq設計基礎知識

 

多線程版本mq

使用LinkedBlockingDeque模擬實現多線程的方式讀寫中間件

package com.mayikt.netty;

import com.alibaba.fastjson.JSONObject;

import java.util.concurrent.LinkedBlockingDeque;

/**
 * @ClassName MayiktThreadMQ
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
public class MayiktThreadMQ {

    private static LinkedBlockingDeque<JSONObject> msgs = new LinkedBlockingDeque<JSONObject>();

    public static void main(String[] args) {
        // 生產線程
        Thread producerThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (true) {
                        Thread.sleep(1000);
                        JSONObject data = new JSONObject();
                        data.put("userId", "1234");
                        // 存入消息
                        msgs.offer(data);
                    }
                } catch (Exception e) {

                }

            }
        }, "生產者");
        producerThread.start();
        // 消費者線程
        Thread consumerThread  = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (true) {
                        JSONObject data = msgs.poll();
                        if (data != null) {
                            System.out.println(Thread.currentThread().getName() + "," + data);
                        }
                    }

                } catch (Exception e) {

                }

            }
        }, "消費者");
        consumerThread.start();
    }
}

 

基於網絡通訊版本mq netty實現

 

 生產者

package com.mayikt.netty;

import com.alibaba.fastjson.JSONObject;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * @ClassName MayiktNettyMQProducer
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
public class MayiktNettyMQProducer {
    public void connect(int port, String host) throws Exception {
        //配置客戶端NIO 線程組
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap client = new Bootstrap();
        try {
            client.group(group)
                    // 設置爲Netty客戶端
                    .channel(NioSocketChannel.class)
                    /**
                     * ChannelOption.TCP_NODELAY參數對應於套接字選項中的TCP_NODELAY,該參數的使用與Nagle算法有關。
                     * Nagle算法是將小的數據包組裝爲更大的幀然後進行發送,而不是輸入一次發送一次,因此在數據包不足的時候會等待其他數據的到來,組裝成大的數據包進行發送,雖然該算法有效提高了網絡的有效負載,但是卻造成了延時。
                     * 而該參數的作用就是禁止使用Nagle算法,使用於小數據即時傳輸。和TCP_NODELAY相對應的是TCP_CORK,該選項是需要等到發送的數據量最大的時候,一次性發送數據,適用於文件傳輸。
                     */
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new MayiktNettyMQProducer.NettyClientHandler());
////                            1. 演示LineBasedFrameDecoder編碼器
//                            ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
//                            ch.pipeline().addLast(new StringDecoder());
                        }
                    });

            //綁定端口, 異步連接操作
            ChannelFuture future = client.connect(host, port).sync();
            //等待客戶端連接端口關閉
            future.channel().closeFuture().sync();
        } finally {
            //優雅關閉 線程組
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        int port = 9008;
        MayiktNettyMQProducer client = new MayiktNettyMQProducer();
        try {
            client.connect(port, "127.0.0.1");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public class NettyClientHandler extends ChannelInboundHandlerAdapter {


        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {

            JSONObject data = new JSONObject();
            data.put("type", "producer");
            JSONObject msg = new JSONObject();
            msg.put("userId", "123456");
            msg.put("age", "23");
            data.put("msg", msg);
            // 生產發送數據
            byte[] req = data.toJSONString().getBytes();
            ByteBuf firstMSG = Unpooled.buffer(req.length);
            firstMSG.writeBytes(req);
            ctx.writeAndFlush(firstMSG);
        }

        /**
         * 客戶端讀取到服務器端數據
         *
         * @param ctx
         * @param msg
         * @throws Exception
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf buf = (ByteBuf) msg;
            byte[] req = new byte[buf.readableBytes()];
            buf.readBytes(req);
            String body = new String(req, "UTF-8");
            System.out.println("客戶端接收到服務器端請求:" + body);
        }

        // tcp屬於雙向傳輸

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.close();
        }
    }
}

MQ服務器

package com.mayikt.netty;

import com.alibaba.fastjson.JSONObject;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.apache.commons.lang3.StringUtils;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.concurrent.LinkedBlockingDeque;

/**
 * @ClassName NettyMQServer2021
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
public class MayiktNettyMQServer {
    public void bind(int port) throws Exception {
        /**
         * Netty 抽象出兩組線程池BossGroup和WorkerGroup
         * BossGroup專門負責接收客戶端的連接, WorkerGroup專門負責網絡的讀寫。
         */
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        try {
            bootstrap.group(bossGroup, workerGroup)
                    // 設定NioServerSocketChannel 爲服務器端
                    .channel(NioServerSocketChannel.class)
                    //BACKLOG用於構造服務端套接字ServerSocket對象,標識當服務器請求處理線程全滿時,
                    //用於臨時存放已完成三次握手的請求的隊列的最大長度。如果未設置或所設置的值小於1,Java將使用默認值50。
                    .option(ChannelOption.SO_BACKLOG, 100)
                    // 服務器端監聽數據回調Handler
                    .childHandler(new MayiktNettyMQServer.ChildChannelHandler());
            //綁定端口, 同步等待成功;
            ChannelFuture future = bootstrap.bind(port).sync();
            System.out.println("當前服務器端啓動成功...");
            //等待服務端監聽端口關閉
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //優雅關閉 線程組
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            // 設置異步回調監聽
            ch.pipeline().addLast(new MayiktNettyMQServer.MayiktServerHandler());
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 9008;
        new MayiktNettyMQServer().bind(port);
    }

    private static final String type_consumer = "consumer";

    private static final String type_producer = "producer";
    private static LinkedBlockingDeque<String> msgs = new LinkedBlockingDeque<>();
    private static ArrayList<ChannelHandlerContext> ctxs = new ArrayList<>();

    // 生產者投遞消息的:topicName
    public class MayiktServerHandler extends SimpleChannelInboundHandler<Object> {

        /**
         * 服務器接收客戶端請求
         *
         * @param ctx
         * @param data
         * @throws Exception
         */
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, Object data)
                throws Exception {
            JSONObject clientMsg = getData(data);
            String type = clientMsg.getString("type");
            switch (type) {
                case type_producer:
                    producer(clientMsg);
                    break;
                case type_consumer:
                    consumer(ctx);
                    break;
            }
        }

        private void consumer(ChannelHandlerContext ctx) {
            // 保存消費者連接
            ctxs.add(ctx);
            // 主動拉取mq服務器端緩存中沒有被消費的消息
            String data = msgs.poll();
            if (StringUtils.isEmpty(data)) {
                return;
            }
            // 將該消息發送給消費者
            byte[] req = data.getBytes();
            ByteBuf firstMSG = Unpooled.buffer(req.length);
            firstMSG.writeBytes(req);
            ctx.writeAndFlush(firstMSG);
        }

        private void producer(JSONObject clientMsg) {
            // 緩存生產者投遞 消息
            String msg = clientMsg.getString("msg");
            msgs.offer(msg);

            //需要將該消息推送消費者
            ctxs.forEach((ctx) -> {
                // 將該消息發送給消費者
                String data = msgs.poll();
                if (data == null) {
                    return;
                }
                byte[] req = data.getBytes();
                ByteBuf firstMSG = Unpooled.buffer(req.length);
                firstMSG.writeBytes(req);
                ctx.writeAndFlush(firstMSG);
            });
        }

        private JSONObject getData(Object data) throws UnsupportedEncodingException {
            ByteBuf buf = (ByteBuf) data;
            byte[] req = new byte[buf.readableBytes()];
            buf.readBytes(req);
            String body = new String(req, "UTF-8");
            return JSONObject.parseObject(body);
        }


        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            ctx.flush();
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
                throws Exception {

            ctx.close();
        }
    }
}

消費者

package com.mayikt.netty;

import com.alibaba.fastjson.JSONObject;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * @ClassName MayiktNettyMQProducer
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
public class

MayiktNettyMQConsumer {
    public void connect(int port, String host) throws Exception {
        //配置客戶端NIO 線程組
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap client = new Bootstrap();
        try {
            client.group(group)
                    // 設置爲Netty客戶端
                    .channel(NioSocketChannel.class)
                    /**
                     * ChannelOption.TCP_NODELAY參數對應於套接字選項中的TCP_NODELAY,該參數的使用與Nagle算法有關。
                     * Nagle算法是將小的數據包組裝爲更大的幀然後進行發送,而不是輸入一次發送一次,因此在數據包不足的時候會等待其他數據的到來,組裝成大的數據包進行發送,雖然該算法有效提高了網絡的有效負載,但是卻造成了延時。
                     * 而該參數的作用就是禁止使用Nagle算法,使用於小數據即時傳輸。和TCP_NODELAY相對應的是TCP_CORK,該選項是需要等到發送的數據量最大的時候,一次性發送數據,適用於文件傳輸。
                     */
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new MayiktNettyMQConsumer.NettyClientHandler());
////                            1. 演示LineBasedFrameDecoder編碼器
//                            ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
//                            ch.pipeline().addLast(new StringDecoder());
                        }
                    });

            //綁定端口, 異步連接操作
            ChannelFuture future = client.connect(host, port).sync();
            //等待客戶端連接端口關閉
            future.channel().closeFuture().sync();
        } finally {
            //優雅關閉 線程組
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        int port = 9008;
        MayiktNettyMQConsumer client = new MayiktNettyMQConsumer();
        try {
            client.connect(port, "127.0.0.1");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public class NettyClientHandler extends ChannelInboundHandlerAdapter {


        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {

            JSONObject data = new JSONObject();
            data.put("type", "consumer");
            // 生產發送數據
            byte[] req = data.toJSONString().getBytes();
            ByteBuf firstMSG = Unpooled.buffer(req.length);
            firstMSG.writeBytes(req);
            ctx.writeAndFlush(firstMSG);
        }

        /**
         * 客戶端讀取到服務器端數據
         *
         * @param ctx
         * @param msg
         * @throws Exception
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf buf = (ByteBuf) msg;
            byte[] req = new byte[buf.readableBytes()];
            buf.readBytes(req);
            String body = new String(req, "UTF-8");
            System.out.println("客戶端接收到服務器端請求:" + body);
        }

        // tcp屬於雙向傳輸

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.close();
        }
    }
}

 

基於netty實現mq

消費者netty客戶端與nettyServer端MQ服務器端保持長連接,MQ服務器端保存

消費者連接。

生產者netty客戶端發送請求給nettyServer端MQ服務器端,MQ服務器端在將該

消息內容發送給消費者。

 

 

 

body:{"msg":{"userId":"123456","age":"23"},"type":"producer",”topic”:””}

生產者投遞消息給MQ服務器端,MQ服務器端需要緩存該消息

如果mq服務器端宕機之後,消息如何保證不丟失:

持久化機制

如果mq接收到生產者投遞消息,如果消費者不在的情況下,該消息是否會丟失?

不會丟失,消息確認機制 必須要消費者消費該消息成功之後,在通知給mq服務器端,刪除該消息。而kafka即使讀取消息後也不會立刻刪除,而是設置該消息被消費了,一定時間後統一刪除。

消費者已經和mq服務器保持長連接:Mq服務器端將該消息推送消費者。

消費者第一次剛啓動的時候,消費者會主動拉取消息。

Mq如何實現抗高併發思想

Mq消費者根據自身能力情況 ,拉取mq服務器端消息消費。

默認的情況下是取出一條消息。

缺點:存在延遲的問題

需要考慮mq消費者提高速率的問題:

如何消費者提高速率:消費者實現集羣、消費者批量獲取消息即可。

 

RabbitMQ

RabbitMQ基本介紹

RabbitMQ是實現了高級消息隊列協議(AMQP)的開源消息代理軟件(亦稱面向消息的中間件),RabbitMQ服務器是用Erlang語言編寫的。

RabitMQ官方網站:

https://www.rabbitmq.com/

 

1.點對點(簡單)的隊列

2.工作(公平性)隊列模式

3.發佈訂閱模式

4.路由模式Routing

5.通配符模式Topics

6.RPC
https://www.rabbitmq.com/getstarted.html

 

 

 

RabbitMQ環境的基本安裝

1.下載並安裝erlang,下載地址:http://www.erlang.org/download

2.配置erlang環境變量信息

  新增環境變量ERLANG_HOME=erlang的安裝地址

  將%ERLANG_HOME%\bin加入到path中

3.下載並安裝RabbitMQ,下載地址:http://www.rabbitmq.com/download.html

注意: RabbitMQ 它依賴於Erlang,需要先安裝Erlang。

https://www.rabbitmq.com/install-windows.html

如何啓動Rabbitmq

net start RabbitMQ

啓動Rabbitmq常見問題

如果rabbitmq 啓動成功無法訪問 管理平臺頁面

進入到F:\path\rabbitmq\rabbitmq\rabbitmq_server-3.6.9\sbin>

執行

rabbitmq-plugins enable rabbitmq_management

rabbitmqctl start_app

 

 

 

 

Rabbitmq管理平臺中心

RabbitMQ 管理平臺地址 http://127.0.0.1:15672

默認賬號:guest/guest  用戶可以自己創建新的賬號

 

Virtual Hosts:

像mysql有數據庫的概念並且可以指定用戶對庫和表等操作的權限。那RabbitMQ呢?

RabbitMQ也有類似的權限管理。在RabbitMQ中可以虛擬消息服務器VirtualHost,每

個VirtualHost相當月一個相對獨立的RabbitMQ服務器,每個VirtualHost之間是相互

隔離。exchange、queue、message不能互通。

默認的端口15672:rabbitmq管理平臺端口號

默認的端口5672: rabbitmq消息中間內部通訊的端口
默認的端口號25672  rabbitmq集羣的端口號

RabbitMQ常見名詞

/Virtual Hosts---分類

/隊列 存放我們消息

Exchange 分派我們消息在那個隊列存放起來 類似於nginx

15672---rabbitmq控制檯管理平臺 http協議

25672rabbitmq 集羣通信端口號

Amqp 5672 rabbitmq內部通信的一個端口號

 

RabbitMQ創建賬戶

RabbitMQ平臺創建Virtual Hosts

RabbitMQ平臺創建消息隊列

 

快速入門RabbitMQ簡單隊列

 

首先需要再RabbitMQ平臺創建Virtual Hosts 和隊列。

/myVirtualHosts

----訂單隊列

----支付隊列

  1. 在RabbitMQ平臺創建一個隊列;
  2. 在編寫生產者代碼
  3. 在編寫消費者代碼

下列模式用到的所有的依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mayikt</groupId>
    <artifactId>mayikt-mq</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>


    <dependencies>
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.54.Final</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.25</version>
            <scope>compile</scope>
        </dependency>

    </dependencies>


</project>

下面的程序是在普通maven工程下創建,使用到的隊列,交換機,虛擬主機都要我們手動在mq管理界面(port:15672)創建

公共類:

package com.mayikt.rabbitmq;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @ClassName RabbitMQConnection
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
public class RabbitMQConnection {

    /**
     * 創建連接
     *
     * @return
     * @throws IOException
     * @throws TimeoutException
     */
    public static Connection getConnection() throws IOException, TimeoutException {
        //1.創建connectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //2.配置Host
        connectionFactory.setHost("127.0.0.1");
        //3.設置Port
        connectionFactory.setPort(5672);
        //4.設置賬戶和密碼
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        //5.設置VirtualHost
        connectionFactory.setVirtualHost("/myVirtualHost");
        return connectionFactory.newConnection();
    }
}

 

點對點模式

 

 

package com.mayikt.demo00;

import com.mayikt.rabbitmq.RabbitMQConnection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @ClassName Producer 演示消息確認機制
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
public class Producer {
    private static final String QUEUE_NAME = "test_quene";

    public static void main(String[] args) {
        try {
            //1.創建一個新連接
            Connection connection = RabbitMQConnection.getConnection();

            //2.設置channel
            Channel channel = connection.createChannel();
            //3.發送消息
            String msg = "每特教育6666";
            channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
            boolean result = channel.waitForConfirms();
            if (result) {
                System.out.println("消息投遞成功");
            } else {
                System.out.println("消息投遞失敗");
            }
            channel.close();
            connection.close();
        } catch (Exception e) {

        }
    }
}
package com.mayikt.demo00;

import com.mayikt.rabbitmq.RabbitMQConnection;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Consumer {
    private static final String QUEUE_NAME = "test_quene";

    public static void main(String[] args) throws IOException, TimeoutException, IOException, TimeoutException {
        // 1.創建連接
        Connection connection = Producer1.getConnection();
        // 2.設置通道
        final Channel channel = connection.createChannel();
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("消費者獲取消息:" + msg);
//                // 消費者完成 消費者通知給mq服務器端刪除該消息
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 3.監聽隊列
        channel.basicConsume(QUEUE_NAME, false, defaultConsumer);

    }
} 

Work模式

 

 

Producer
package com.mayikt.demo01;

import com.mayikt.rabbitmq.RabbitMQConnection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @ClassName Producer
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
public class Producer {
    private static final String QUEUE_NAME = "mayikt-queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        //1.創建一個新連接
        Connection connection = RabbitMQConnection.getConnection();
        //2.設置channel
        Channel channel = connection.createChannel();
        //3.發送消息
        for (int i = 0; i < 100; i++) {
            String msg = "每特教育6666:i" + i;
            channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
        }
        System.out.println("消息投遞成功");
        channel.close();
        connection.close();
    }
}
Consumer01
package com.mayikt.demo01;

import com.mayikt.rabbitmq.RabbitMQConnection;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Consumer01 {
    private static final String QUEUE_NAME = "mayikt-queue";

    public static void main(String[] args) throws IOException, TimeoutException, IOException, TimeoutException {
        // 1.創建連接
        Connection connection = RabbitMQConnection.getConnection();
        // 2.設置通道
        Channel channel = connection.createChannel();
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {

                }
                String msg = new String(body, "UTF-8");
                System.out.println("消費者獲取消息:" + msg);
            }
        };
        // 3.監聽隊列
        channel.basicConsume(QUEUE_NAME, true, defaultConsumer);

    }
}
Consumer02
package com.mayikt.demo01;

import com.mayikt.rabbitmq.RabbitMQConnection;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Consumer02 {
    private static final String QUEUE_NAME = "mayikt-queue";

    public static void main(String[] args) throws IOException, TimeoutException, IOException, TimeoutException {
        // 1.創建連接
        Connection connection = RabbitMQConnection.getConnection();
        // 2.設置通道
        Channel channel = connection.createChannel();
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {

                String msg = new String(body, "UTF-8");
                System.out.println("消費者獲取消息:" + msg);
            }
        };
        // 3.監聽隊列
        channel.basicConsume(QUEUE_NAME, true, defaultConsumer);

    }
}

測試結果

測試結果:

1、  消費者1和消費者2獲取到的消息內容是不同的,同一個消息只能被一個消費者獲取。

2、  消費者1和消費者2獲取到的消息的數量是相同的,一個是奇數一個是偶數。

其實,這樣是不合理的,應該是消費者1要比消費者2獲取到的消息多才對。

Work模式的“能者多勞”

Consumer1
package com.mayikt.demo02;

import com.mayikt.rabbitmq.RabbitMQConnection;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Consumer1 {
    private static final String QUEUE_NAME = "mayikt-queue";

    public static void main(String[] args) throws IOException, TimeoutException, IOException, TimeoutException {
        // 1.創建連接
        Connection connection = RabbitMQConnection.getConnection();
        // 2.設置通道
        Channel channel = connection.createChannel();
        //指定我們消費者每次批量獲取消息
        channel.basicQos(2);
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {

                String msg = new String(body, "UTF-8");
                System.out.println("消費者獲取消息:" + msg);
                try {
// 消費者完成 刪除該消息
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }catch (Exception e){

                }
//
            }
        };
        // 3.監聽隊列
        channel.basicConsume(QUEUE_NAME, false, defaultConsumer);

    }
}
Consumer2
package com.mayikt.demo02;

import com.mayikt.rabbitmq.RabbitMQConnection;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Consumer2 {
    private static final String QUEUE_NAME = "mayikt-queue";

    public static void main(String[] args) throws IOException, TimeoutException, IOException, TimeoutException {
        // 1.創建連接
        Connection connection = RabbitMQConnection.getConnection();
        // 2.設置通道
        Channel channel = connection.createChannel();
        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(1000);
                } catch (Exception e) {

                }
                String msg = new String(body, "UTF-8");
                System.out.println("消費者獲取消息:" + msg);
                // 消費者完成 刪除該消息
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 3.監聽隊列
        channel.basicConsume(QUEUE_NAME, false, defaultConsumer);

    }
}
package com.mayikt.demo02;

import com.mayikt.rabbitmq.RabbitMQConnection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @ClassName Producer
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
public class Producer {
    private static final String QUEUE_NAME = "mayikt-queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        //1.創建一個新連接
        Connection connection = RabbitMQConnection.getConnection();
        //2.設置channel
        Channel channel = connection.createChannel();
        //3.發送消息
        for (int i = 0; i < 10; i++) {
            String msg = "每特教育6666:i" + i;
            channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
        }
        System.out.println("消息投遞成功");
        channel.close();
        connection.close();
    }
}

 

 

ProducerFanout
package com.mayikt.demo03;

import com.mayikt.rabbitmq.RabbitMQConnection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ProducerFanout {

    /**
     * 定義交換機的名稱
     */
    private static final String EXCHANGE_NAME = "fanout_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        //  創建Connection
        Connection connection = RabbitMQConnection.getConnection();
        // 創建Channel
        Channel channel = connection.createChannel();
        // 通道關聯交換機
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout", true);  //第二個參數type:交換機類型,常見的如fanout、direct、topic

        String msg = "每特教育6666";
        channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());
        channel.close();
        connection.close();
    }

}
package com.mayikt.demo03;

import com.mayikt.rabbitmq.RabbitMQConnection;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class MailConsumer {
    /**
     * 定義郵件隊列
     */
    private static final String QUEUE_NAME = "fanout_email_queue";
    /**
     * 定義交換機的名稱
     */
    private static final String EXCHANGE_NAME = "fanout_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        System.out.println("郵件消費者...");
        // 創建我們的連接
        Connection connection = RabbitMQConnection.getConnection();
        // 創建我們通道
        final Channel channel = connection.createChannel();
        // 關聯隊列消費者關聯隊列
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("郵件消費者獲取消息:" + msg);
            }
        };
        // 開始監聽消息 自動簽收
        channel.basicConsume(QUEUE_NAME, true, defaultConsumer);

    }
}
SmsConsumer
package com.mayikt.demo03;

import com.mayikt.rabbitmq.RabbitMQConnection;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class SmsConsumer {
    /**
     * 定義短信隊列
     */
    private static final String QUEUE_NAME = "fanout_email_sms";
    /**
     * 定義交換機的名稱
     */
    private static final String EXCHANGE_NAME = "fanout_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        System.out.println("短信消費者...");
        // 創建我們的連接
        Connection connection = RabbitMQConnection.getConnection();
        // 創建我們通道
        final Channel channel = connection.createChannel();
        // 關聯隊列消費者關聯隊列
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("短信消費者獲取消息:" + msg);
            }
        };
        // 開始監聽消息 自動簽收
        channel.basicConsume(QUEUE_NAME, true, defaultConsumer);

    }
}

 

 

四、路由模式

1、圖示

註釋:Direct Exchange – 處理路由鍵。需要將一個隊列綁定到交換機上,要求該消息與一個特定的路由鍵完全匹配。這是一個完整的匹配。如果一個隊列綁定到該交換機上要求路由鍵 “dog”,則只有被標記爲“dog”的消息才被轉發,不會轉發dog.puppy,也不會轉發dog.guard,只會轉發dog。 

ProducerDirect
package com.mayikt.demo04;

import com.mayikt.rabbitmq.RabbitMQConnection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ProducerDirect {

    /**
     * 定義交換機的名稱
     */
    private static final String EXCHANGE_NAME = "direct_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        //  創建Connection
        Connection connection = RabbitMQConnection.getConnection();
        // 創建Channel
        Channel channel = connection.createChannel();
        // 通道關聯交換機
        channel.exchangeDeclare(EXCHANGE_NAME, "direct", true);
        String msg = "每特教育6666";
        channel.basicPublish(EXCHANGE_NAME, "email", null, msg.getBytes());  //只發送給郵件這個隊列
        channel.close();
        connection.close();
    }

}
MailConsumer
package com.mayikt.demo04;

import com.mayikt.rabbitmq.RabbitMQConnection;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class MailConsumer {
    /**
     * 定義郵件隊列
     */
    private static final String QUEUE_NAME = "direct_email_queue";
    /**
     * 定義交換機的名稱
     */
    private static final String EXCHANGE_NAME = "direct_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        System.out.println("郵件消費者...");
        // 創建我們的連接
        Connection connection = RabbitMQConnection.getConnection();
        // 創建我們通道
        final Channel channel = connection.createChannel();
        // 關聯隊列消費者關聯隊列
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "email");
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("郵件消費者獲取消息:" + msg);
            }
        };
        // 開始監聽消息 自動簽收
        channel.basicConsume(QUEUE_NAME, true, defaultConsumer);

    }
}
SmsConsumer
package com.mayikt.demo04;

import com.mayikt.rabbitmq.RabbitMQConnection;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class SmsConsumer {
    /**
     * 定義短信隊列
     */
    private static final String QUEUE_NAME = "direct_sms_queue";
    /**
     * 定義交換機的名稱
     */
    private static final String EXCHANGE_NAME = "direct_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        System.out.println("短信消費者...");
        // 創建我們的連接
        Connection connection = RabbitMQConnection.getConnection();
        // 創建我們通道
        final Channel channel = connection.createChannel();
        // 關聯隊列消費者關聯隊列
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "sms");
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("短信消費者獲取消息:" + msg);
            }
        };
        // 開始監聽消息 自動簽收
        channel.basicConsume(QUEUE_NAME, true, defaultConsumer);

    }
}

 

 

 

ProducerTopic

package com.mayikt.demo06;

import com.mayikt.rabbitmq.RabbitMQConnection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ProducerTopic {

/**
* 定義交換機的名稱
*/
private static final String EXCHANGE_NAME = "topic_exchange";

public static void main(String[] args) throws IOException, TimeoutException {
// 創建Connection
Connection connection = RabbitMQConnection.getConnection();
// 創建Channel
Channel channel = connection.createChannel();
// 通道關聯交換機
channel.exchangeDeclare(EXCHANGE_NAME, "topic", true);
String msg = "每特教育6666";
channel.basicPublish(EXCHANGE_NAME, "mayikt.sms", null, msg.getBytes());
channel.close();
connection.close();
}

}
MailConsumer

package com.mayikt.demo06;

import com.mayikt.rabbitmq.RabbitMQConnection;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class MailConsumer {
/**
* 定義郵件隊列
*/
private static final String QUEUE_NAME = "topic_email_queue";
/**
* 定義交換機的名稱
*/
private static final String EXCHANGE_NAME = "topic_exchange";

public static void main(String[] args) throws IOException, TimeoutException {
System.out.println("郵件消費者...");
// 創建我們的連接
Connection connection = RabbitMQConnection.getConnection();
// 創建我們通道
final Channel channel = connection.createChannel();
// 關聯隊列消費者關聯隊列
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "mayikt.*");
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "UTF-8");
System.out.println("郵件消費者獲取消息:" + msg);
}
};
// 開始監聽消息 自動簽收
channel.basicConsume(QUEUE_NAME, true, defaultConsumer);

}
}
SmsConsumer
package com.mayikt.demo06;

import com.mayikt.rabbitmq.RabbitMQConnection;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class SmsConsumer {
/**
* 定義短信隊列
*/
private static final String QUEUE_NAME = "topic_sms_queue";
/**
* 定義交換機的名稱
*/
private static final String EXCHANGE_NAME = "topic_exchange";

public static void main(String[] args) throws IOException, TimeoutException {
System.out.println("短信消費者...");
// 創建我們的連接
Connection connection = RabbitMQConnection.getConnection();
// 創建我們通道
final Channel channel = connection.createChannel();
// 關聯隊列消費者關聯隊列
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "meite.*");
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "UTF-8");
System.out.println("短信消費者獲取消息:" + msg);
}
};
// 開始監聽消息 自動簽收
channel.basicConsume(QUEUE_NAME, true, defaultConsumer);

}
}

 

RPC模式

https://blog.csdn.net/hry2015/article/details/79199294

 

 

1. RPC客戶端啓動後,創建一個匿名、獨佔的、回調的隊列
2. RPC客戶端設置消息的2個屬性:replyTo和correlationId,然後將消息發送到隊列rpc_queue
3. RPC服務端在隊列rpc_queue上等待消息。RPC服務端處理完收到消息後,然後將處理結果封裝成消息發送到replyTo指定的隊列上,並且此消息帶上correlationId(此值爲收到消息裏的correlationId)
4. RPC客戶端在隊列replyTo上等待消息,當收到消息後,它會判斷收到消息的correlationId。如果值和自己之前發送的一樣,則這個值就是RPC的處理結果

  

 

 在rabbitmq情況下:RabbitMQ如何保證消息不丟失

 

 

 

使用消息確認機制+持久技術

A.消費者確認收到消息機制

channel.basicConsume(QUEUE_NAME, false, defaultConsumer);

注:第二個參數值爲false代表關閉RabbitMQ的自動應答機制,改爲手動應答。

在處理完消息時,返回應答狀態,true表示爲自動應答模式。

channel.basicAck(envelope.getDeliveryTag(), false);

B.生產者確認投遞消息成功 使用Confirm機制 或者事務消息

Confirm機制 同步或者是異步的形式

 

2.RabbitMQ默認創建是持久化的

 

代碼中設置 durable爲true

 

參數名稱詳解:

durable是否持久化 durable爲持久化、 Transient 不持久化

autoDelete 是否自動刪除,當最後一個消費者斷開連接之後隊列是否自動被刪除,可以通過RabbitMQ Management,查看某個隊列的消費者數量,當consumers = 0時隊列就會自動刪除

  1. 使用rabbitmq事務消息;
            channel.txSelect();
            channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
//            int i = 1 / 0;
           
channel.txCommit();

 

 

相關核心代碼

生產者
public class Producer {
    private static final String QUEUE_NAME = "mayikt-queue";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //1.創建一個新連接
       
Connection connection = RabbitMQConnection.getConnection();
        //2.設置channel
       
Channel channel = connection.createChannel();
        //3.發送消息
       
String msg = "每特教育6666";
channel.confirmSelect();

        channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
        boolean result = channel.waitForConfirms();
        if (result) {
            System.out.println("消息投遞成功");
        } else {
            System.out.println("消息投遞失敗");
        }
        channel.close();
        connection.close();
    }
}

 

 

消費者
public class Consumer {
    private static final String QUEUE_NAME = "mayikt-queue";

    public static void main(String[] args) throws IOException, TimeoutException, IOException, TimeoutException {
        // 1.創建連接
       
Connection connection = RabbitMQConnection.getConnection();
        // 2.設置通道
       
Channel channel = connection.createChannel();
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("消費者獲取消息:" + msg);
                // 消費者完成 消費該消息
               
channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 3.監聽隊列
       
channel.basicConsume(QUEUE_NAME, false, defaultConsumer);

    }
}

 

 

 

 

SpringBoot整合RabbitMQ

 

 

 兩個消費者,一個生產者

Maven依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mayikt</groupId>
    <artifactId>mayikt-sp-rabbitmq</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>mayikt-producer</module>
        <module>email-consumer</module>
        <module>sms-consumer</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>
    <dependencies>

        <!-- springboot-web組件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 添加springboot對amqp的支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <!--fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.49</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

生產者

 

 yml

spring:
  rabbitmq:
    ####連接地址
    host: 127.0.0.1
    ####端口號
    port: 5672
    ####賬號
    username: guest
    ####密碼
    password: guest
    ###
    virtual-host: /myVirtualHost
server:
  port: 9092
package com.mayikt.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * @ClassName RabbitMQConfig
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@Component
public class RabbitMQConfig {

    /**
     * 定義交換機
     */
    private String EXCHANGE_SPRINGBOOT_NAME = "/mayikt_ex";


    /**
     * 短信隊列
     */
    private String FANOUT_SMS_QUEUE = "fanout_sms_queue";
    /**
     * 郵件隊列
     */
    private String FANOUT_EMAIL_QUEUE = "fanout_email_queue";

    // 1.注入隊列和交換機注入到spring容器中
    // 2.關聯交換機  <bean id="smsQueue" class="";>

    /**
     * 郵件和短信隊列注入到spring容器中
     *
     * @return
     */
    @Bean
    public Queue smsQueue() {
        return new Queue(FANOUT_SMS_QUEUE);
    }

    @Bean
    public Queue emailQueue() {
        return new Queue(FANOUT_EMAIL_QUEUE);
    }

    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange(EXCHANGE_SPRINGBOOT_NAME);
    }

    /**
     * 關聯交換機
     * 根據參數名稱 ioc獲取 Queue對象
     */
    @Bean
    public Binding BindingSmsFanoutExchange(Queue smsQueue, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(smsQueue).to(fanoutExchange);
    }

    @Bean
    public Binding BindingEmailFanoutExchange(Queue emailQueue, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(emailQueue).to(fanoutExchange);
    }
}
package com.mayikt.entity;

import lombok.Data;
import org.springframework.stereotype.Component;

import java.io.Serializable;

/**
 * @ClassName MsgEntity
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@Data
public class MsgEntity implements Serializable {
    private String msgId;
    private String userId;
    private String phone;
    private String email;

    public MsgEntity(String msgId, String userId, String phone, String email) {
        this.msgId = msgId;
        this.userId = userId;
        this.phone = phone;
        this.email = email;
    }
}
package com.mayikt.service;

import com.mayikt.entity.MsgEntity;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

/**
 * @ClassName ProducerService
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@RestController
public class ProducerService {

    @Autowired
    private AmqpTemplate amqpTemplate;

    @RequestMapping("/sendMsg")
    public void sendMsg() {
        /**
         * 參數1 交換機名稱
         * 參數2 路由key
         * 參數3 發送內容
         */
        MsgEntity msgEntity = new MsgEntity(UUID.randomUUID().toString(),
                "1234", "181111111", "[email protected]");
        amqpTemplate.convertAndSend("/mayikt_ex", "", msgEntity);
    }
}
package com.mayikt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @ClassName AppProducer
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@SpringBootApplication
public class AppProducer {
    public static void main(String[] args) {
        SpringApplication.run(AppProducer.class);
    }
}

 

spring:
  rabbitmq:
    ####連接地址
    host: 127.0.0.1
    ####端口號
    port: 5672
    ####賬號
    username: guest
    ####密碼
    password: guest
    ###
    virtual-host: /myVirtualHost

server:
  port: 8081
package com.mayikt.entity;

import lombok.Data;

import java.io.Serializable;

/**
 * @ClassName MsgEntity
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@Data
public class MsgEntity implements Serializable {
    private String msgId;
    private String userId;
    private String phone;
    private String email;

    public MsgEntity(String msgId, String userId, String phone, String email) {
        this.msgId = msgId;
        this.userId = userId;
        this.phone = phone;
        this.email = email;
    }
}
package com.mayikt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @ClassName AppEmailConsumer
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@SpringBootApplication
public class AppEmailConsumer {
    public static void main(String[] args) {
        SpringApplication.run(AppEmailConsumer.class);
    }
}
package com.mayikt;

import com.mayikt.entity.MsgEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * @ClassName FanoutSmsConsumer
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@Slf4j
@Component
@RabbitListener(queues = "fanout_email_queue")
public class FanoutEmailConsumer {

    @RabbitHandler
    public void process(MsgEntity msgEntity) {
        log.info("email:msgEntity:" + msgEntity);
    }
}

 

spring:
  rabbitmq:
    ####連接地址
    host: 127.0.0.1
    ####端口號
    port: 5672
    ####賬號
    username: guest
    ####密碼
    password: guest
    ###
    virtual-host: /myVirtualHost

server:
  port: 8082
package com.mayikt.entity;

import lombok.Data;

import java.io.Serializable;

/**
 * @ClassName MsgEntity
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@Data
public class MsgEntity implements Serializable {
    private String msgId;
    private String userId;
    private String phone;
    private String email;

    public MsgEntity(String msgId, String userId, String phone, String email) {
        this.msgId = msgId;
        this.userId = userId;
        this.phone = phone;
        this.email = email;
    }
}
package com.mayikt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @ClassName AppEmailConsumer
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@SpringBootApplication
public class AppSMSConsumer {
    public static void main(String[] args) {
        SpringApplication.run(AppSMSConsumer.class);
    }
}
package com.mayikt;

import com.mayikt.entity.MsgEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * @ClassName FanoutSmsConsumer
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@Slf4j
@Component
@RabbitListener(queues = "fanout_sms_queue")
public class FanoutEmailConsumer {

    @RabbitHandler
    public void process(MsgEntity msgEntity) {
        log.info("sms:msgEntity:" + msgEntity);
    }
}

需要注意的是springboot可以自動生成交換機和隊列,無需手動配置。

 

 

 生產者如何獲取消費結果

 

 

RabbitMQ實戰解決方案

 

 下面的代碼演示的是生產者生產消息給mq中間件(通過服務的方式),消費者訂閱消費,並插入數據庫,生產者通過接口和id判斷數據庫有沒有相對應的消息來判斷有沒有被消費。

 

 yml

這裏面加入了自定義消費失敗重試策略

spring:
  rabbitmq:
    ####連接地址
    host: 127.0.0.1
    ####端口號
    port: 5672
    ####賬號
    username: guest
    ####密碼
    password: guest
    ### 地址
    virtual-host: /myVirtualHost
    listener:
      simple:
        retry:
          ####開啓消費者(程序出現異常的情況下會)進行重試
          enabled: true
          ####最大重試次數
          max-attempts: 5
          ####重試間隔時間
          initial-interval: 3000
        acknowledge-mode: manual
  datasource:
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
server:
  port: 9001
package com.mayikt.config;


import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * @ClassName RabbitMQConfig
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@Component
public class RabbitMQConfig {
    /**
     * 定義交換機
     */
    private String EXCHANGE_SPRINGBOOT_NAME = "/mayikt_order";


    /**
     * 訂單隊列
     */
    private String FANOUT_ORDER_QUEUE = "fanout_order_queue";


    /**
     * 配置orderQueue
     *
     * @return
     */
    @Bean
    public Queue orderQueue() {
        return new Queue(FANOUT_ORDER_QUEUE);
    }


    /**
     * 配置fanoutExchange
     *
     * @return
     */
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange(EXCHANGE_SPRINGBOOT_NAME);
    }

    // 綁定交換機 orderQueue
    @Bean
    public Binding bindingOrderFanoutExchange(Queue orderQueue, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(orderQueue).to(fanoutExchange);
    }

}

消費者處理了重複提交的問題,但是這種方法仍有風險,可以靠唯一id解決。

int i = 1 / 0;  可以演示失敗重試。
package com.mayikt.consumer;

import com.alibaba.fastjson.JSONObject;
import com.mayikt.entity.OrderEntity;
import com.mayikt.manager.OrderManager;
import com.mayikt.mapper.OrderMapper;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * @ClassName fanout_sms_queue
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@Slf4j
@Component
@RabbitListener(queues = "fanout_order_queue")
public class FanoutOrderConsumer {

    @Autowired
    private OrderManager orderManager;
    @Autowired
    private OrderMapper orderMapper;

    @RabbitHandler
    public void process(OrderEntity orderEntity, Message message, Channel channel) throws IOException {
//        try {
        log.info(">>orderEntity:{}<<", orderEntity.toString());
        String orderId = orderEntity.getOrderId();
        if (StringUtils.isEmpty(orderId)) {
            return;
        }
        OrderEntity dbOrderEntity = orderMapper.getOrder(orderId);
        if (dbOrderEntity != null) {//重試的過程中,爲了避免業務邏輯重複執行,建議提前全局id提前查詢,如果存在的情況下,就無需再繼續做該流程。

            log.info("另外消費者已經處理過該業務邏輯");
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            return;
        }
//        int i = 1 / 0;
        int result = orderManager.addOrder(orderEntity);

        log.info(">>插入數據庫中數據成功<<");
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
//        } catch (Exception e) {
//            // 記錄該消息日誌形式  存放數據庫db中、後期通過定時任務實現消息補償、人工實現補償
//
//            //將該消息存放到死信隊列中,單獨寫一個死信消費者實現消費。
//        }
    }
}

 

package com.mayikt.entity;

import lombok.Data;

import java.io.Serializable;

@Data
public class OrderEntity implements Serializable {
    private int id;
    private String orderName;
    private String orderId;

    public OrderEntity(String orderName, String orderId) {
        this.orderName = orderName;
        this.orderId = orderId;
    }

    public OrderEntity() {

    }

}
package com.mayikt.manager;

import com.mayikt.entity.OrderEntity;
import com.mayikt.mapper.OrderMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * @ClassName OrderManager
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@Component
public class OrderManager {
    @Autowired
    private OrderMapper orderMapper;

    @Transactional
    public int addOrder(OrderEntity orderEntity) {
        return orderMapper.addOrder(orderEntity);
    }
}
package com.mayikt.mapper;

import com.mayikt.entity.OrderEntity;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;

public interface OrderMapper {
    @Insert("insert order_info values (null,#{orderName},#{orderId})")
    int addOrder(OrderEntity orderEntity);

    @Select("SELECT * from order_info where orderId=#{orderId} ")
    OrderEntity getOrder(String orderId);
}
package com.mayikt.producer;

import com.alibaba.fastjson.JSONObject;
import com.mayikt.entity.OrderEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @ClassName OrderProducer
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@Component
@Slf4j
public class OrderProducer implements RabbitTemplate.ConfirmCallback {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        String id = correlationData.getId();
        log.info("id:" + id);
        System.out.println(id);
    }

    /**
     * 使用mq發送消息
     *
     * @param orderName
     * @param orderId
     */
    public void sendMsg(String orderName, String orderId) {
        OrderEntity orderEntity = new OrderEntity(orderName, orderId);
        rabbitTemplate.convertAndSend("/mayikt_order", "", orderEntity, message -> {
            return message;
        });
//        CorrelationData correlationData = new CorrelationData();
//        correlationData.setId(JSONObject.toJSONString(orderEntity));
//        rabbitTemplate.convertAndSend("/mayikt_order", "", orderEntity,correlationData);
    }

}
package com.mayikt.service;

import com.alibaba.fastjson.JSONObject;
import com.mayikt.entity.OrderEntity;
import com.mayikt.mapper.OrderMapper;
import com.mayikt.producer.OrderProducer;
import lombok.extern.slf4j.Slf4j;
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.amqp.rabbit.support.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class OrderService {



    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private OrderProducer orderProducer;

    @RequestMapping("/sendOrder")
    public String sendOrder() {
        // 生成全局id
        String orderId = System.currentTimeMillis() + "";
        log.info("orderId:{}", orderId);
        String orderName = "每特教育svip課程報名";
        orderProducer.sendMsg(orderName, orderId);
        return orderId;
    }


    /**
     * 前端主動根據orderId定時查詢
     *
     * @param orderId
     * @return
     */
    @RequestMapping("/getOrder")
    public Object getOrder(String orderId) {
        System.out.println(orderId);
        OrderEntity order = orderMapper.getOrder(orderId);
        if (order == null) {
            return "該訂單沒有被消費或者訂單號錯誤!";
        }
        return order;
    }


}

RabbitMQ死信隊列

死信隊列產生的背景

RabbitMQ死信隊列俗稱,備胎隊列;消息中間件因爲某種原因拒收該消息後,可以轉移到死信隊列中存放,死信隊列也可以有交換機和路由key等。

產生死信隊列的原因

  1. 消息投遞到MQ中存放 消息已經過期  消費者沒有及時的獲取到我們消息,消息如果存放到mq服務器中過期之後,會轉移到備胎死信隊列存放。
  2. 隊列達到最大的長度 (隊列容器已經滿了)
  3. 3.       消費者消費多次消息失敗,就會轉移存放到死信隊列中

 

 

 

 

代碼整合 參考 mayikt-springboot-rabbitmq|#中order-dead-letter-queue項目

 

死信隊列的架構原理

死信隊列和普通隊列區別不是很大

普通與死信隊列都有自己獨立的交換機和路由key、隊列和消費者。

區別:

1.生產者投遞消息先投遞到我們普通交換機中,普通交換機在將該消息投到

普通隊列中緩存起來,普通隊列對應有自己獨立普通消費者。

2.如果生產者投遞消息到普通隊列中,普通隊列發現該消息一直沒有被消費者消費

的情況下,在這時候會將該消息轉移到死信(備胎)交換機中,死信(備胎)交換機

對應有自己獨立的 死信(備胎)隊列 對應獨立死信(備胎)消費者。

 

死信隊列應用場景

1.30分鐘訂單超時設計

  1. Redis過期key :
  2. 死信延遲隊列實現:

採用死信隊列,創建一個普通隊列沒有對應的消費者消費消息,在30分鐘過後

就會將該消息轉移到死信備胎消費者實現消費。

備胎死信消費者會根據該訂單號碼查詢是否已經支付過,如果沒有支付的情況下

則會開始回滾庫存操作。

 

 

 

spring:
  rabbitmq:
    ####連接地址
    host: 127.0.0.1
    ####端口號
    port: 5672
    ####賬號
    username: guest
    ####密碼
    password: guest
    ### 地址
    virtual-host: /myVirtualHost
server:
  port: 8080

###模擬演示死信隊列
mayikt:
  dlx:
    exchange: mayikt_dlx_exchange
    queue: mayikt_order_dlx_queue
    routingKey: dlx
  ###備胎交換機
  order:
    exchange: mayikt_order_exchange
    queue: mayikt_order_queue
    routingKey: mayikt.order
package com.mayikt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @ClassName AppDeadLetter
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@SpringBootApplication
public class AppDeadLetter {
    public static void main(String[] args) {
        SpringApplication.run(AppDeadLetter.class);
    }
}
package com.mayikt.producer;

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.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName OrderProducer
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@RestController
public class OrderProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    /**
     * 訂單交換機
     */
    @Value("${mayikt.order.exchange}")
    private String orderExchange;
    /**
     * 訂單路由key
     */
    @Value("${mayikt.order.routingKey}")
    private String orderRoutingKey;

    @RequestMapping("/sendOrder")
    public String sendOrder() {
        String msg = "每特教育牛逼";
        rabbitTemplate.convertAndSend(orderExchange, orderRoutingKey, msg, message -> {
            // 設置消息過期時間 10秒過期
            message.getMessageProperties().setExpiration("10000");
            return message;
        });
        return "success";
    }
}
package com.mayikt.consumer;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class OrderDlxConsumer {

    /**
     * 死信隊列監聽隊列回調的方法
     *
     * @param msg
     */
    @RabbitListener(queues = "mayikt_order_dlx_queue")
    public void orderConsumer(String msg) {
        log.info(">死信隊列消費訂單消息:msg{}<<", msg);
    }
}

註釋掉模式消費失敗

//package com.mayikt.consumer;
//
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.amqp.rabbit.annotation.RabbitListener;
//import org.springframework.stereotype.Component;
//
///**
// * 訂單消費者
// */
//@Component
//@Slf4j
//public class OrderConsumer {
//
//    /**
//     * 監聽隊列回調的方法
//     *
//     * @param msg
//     */
//    @RabbitListener(queues = "mayikt_order_queue")
//    public void orderConsumer(String msg) {
//        log.info(">>正常訂單消費者消息MSG:{}<<", msg);
//    }
//}
package com.mayikt.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class DeadLetterMQConfig {
    /**
     * 訂單交換機
     */
    @Value("${mayikt.order.exchange}")
    private String orderExchange;

    /**
     * 訂單隊列
     */
    @Value("${mayikt.order.queue}")
    private String orderQueue;

    /**
     * 訂單路由key
     */
    @Value("${mayikt.order.routingKey}")
    private String orderRoutingKey;
    /**
     * 死信交換機
     */
    @Value("${mayikt.dlx.exchange}")
    private String dlxExchange;

    /**
     * 死信隊列
     */
    @Value("${mayikt.dlx.queue}")
    private String dlxQueue;
    /**
     * 死信路由
     */
    @Value("${mayikt.dlx.routingKey}")
    private String dlxRoutingKey;

    /**
     * 聲明死信交換機
     *
     * @return DirectExchange
     */
    @Bean
    public DirectExchange dlxExchange() {
        return new DirectExchange(dlxExchange);
    }

    /**
     * 聲明死信隊列
     *
     * @return Queue
     */
    @Bean
    public Queue dlxQueue() {
        return new Queue(dlxQueue);
    }

    /**
     * 聲明訂單業務交換機
     *
     * @return DirectExchange
     */
    @Bean
    public DirectExchange orderExchange() {
        return new DirectExchange(orderExchange);
    }

    /**
     * 聲明訂單隊列
     *
     * @return Queue
     */
    @Bean
    public Queue orderQueue() {
        // 訂單隊列綁定我們的死信交換機
        Map<String, Object> arguments = new HashMap<>(2);
        arguments.put("x-dead-letter-exchange", dlxExchange);
        arguments.put("x-dead-letter-routing-key", dlxRoutingKey);
        return new Queue(orderQueue, true, false, false, arguments);
    }

    /**
     * 綁定死信隊列到死信交換機
     *
     * @return Binding
     */
    @Bean
    public Binding binding() {
        return BindingBuilder.bind(dlxQueue())
                .to(dlxExchange())
                .with(dlxRoutingKey);
    }


    /**
     * 綁定訂單隊列到訂單交換機
     *
     * @return Binding
     */
    @Bean
    public Binding orderBinding() {
        return BindingBuilder.bind(orderQueue())
                .to(orderExchange())
                .with(orderRoutingKey);
    }
}

下面的代碼是使用springboot演示訂閱模式實現異步發送郵件和短信,當然生產者和消費者進行拆開成微服務。

 

 

微服務形式

 

 

 

 

 

spring:
  rabbitmq:
    ####連接地址
    host: 127.0.0.1
    ####端口號
    port: 5672
    ####賬號
    username: guest
    ####密碼
    password: guest
    ### 地址
    virtual-host: /myVirtualHost
package com.mayikt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @ClassName App
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class);
    }
}
package com.mayikt.producer;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName FanoutProducer
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@RestController
public class FanoutProducer {

    @Autowired
    private AmqpTemplate amqpTemplate;

    /**
     * 發送消息
     *
     * @return
     */
    @RequestMapping("/sendMsg")
    public String sendMsg(String msg) {
        /**
         * 1.交換機名稱
         * 2.路由key名稱
         * 3.發送內容
         */
        amqpTemplate.convertAndSend("/mayikt_ex", "", msg);
        return "success";
    }
}
package com.mayikt.consumer;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * @ClassName fanout_sms_queue
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@Slf4j
@Component
@RabbitListener(queues = "fanout_sms_queue")
public class FanoutSmsConsumer {

    @RabbitHandler
    public void process(String msg) {
        log.info(">>短信消費者消息msg:{}<<", msg);
    }
}
package com.mayikt.consumer;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * @ClassName FanoutEmailConsumer
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@Slf4j
@Component
@RabbitListener(queues = "fanout_email_queue")
public class FanoutEmailConsumer {

    @RabbitHandler
    public void process(String msg) {
        log.info(">>郵件消費者消息msg:{}<<", msg);
    }
}
package com.mayikt.config;


import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * @ClassName RabbitMQConfig
 * @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
@Component
public class RabbitMQConfig {
    /**
     * 定義交換機
     */
    private String EXCHANGE_SPRINGBOOT_NAME = "/mayikt_ex";


    /**
     * 短信隊列
     */
    private String FANOUT_SMS_QUEUE = "fanout_sms_queue";
    /**
     * 郵件隊列
     */
    private String FANOUT_EMAIL_QUEUE = "fanout_email_queue";

    /**
     * 配置smsQueue
     *
     * @return
     */
    @Bean
    public Queue smsQueue() {
        return new Queue(FANOUT_SMS_QUEUE);
    }

    /**
     * 配置emailQueue
     *
     * @return
     */
    @Bean
    public Queue emailQueue() {
        return new Queue(FANOUT_EMAIL_QUEUE);
    }

    /**
     * 配置fanoutExchange
     *
     * @return
     */
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange(EXCHANGE_SPRINGBOOT_NAME);
    }

    // 綁定交換機 sms
    @Bean
    public Binding bindingSmsFanoutExchange(Queue smsQueue, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(smsQueue).to(fanoutExchange);
    }

    // 綁定交換機 email
    @Bean
    public Binding bindingEmailFanoutExchange(Queue emailQueue, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(emailQueue).to(fanoutExchange);
    }
}

 

 

 

 

RabbitMQ消息冪等問題

RabbitMQ消息自動重試機制

  1. 當我們消費者處理執行我們業務代碼的時候,如果拋出異常的情況下

在這時候mq會自動觸發重試機制,默認的情況下rabbitmq是無限次數的重試。

需要人爲指定重試次數限制問題

  1. 在什麼情況下消費者需要實現重試策略?

 

A.消費者獲取消息後,調用第三方接口,但是調用第三方接口失敗呢?是否需要重試?

  該情況下需要實現重試策略,網絡延遲只是暫時調用不通,重試多次有可能會調用通。

B.消費者獲取消息後,因爲代碼問題拋出數據異常,是否需要重試?

  該情況下是不需要實現重試策略,就算重試多次,最終還是失敗的。可以將日誌存放起來,後期通過定時任務或者人工補償形式。如果是重試多次還是失敗消息,需要重新發布消費者版本實現消費。

可以使用死信隊列

 

 

Mq在重試的過程中,有可能會引發消費者重複消費的問題。

Mq消費者需要解決 冪等性問題

冪等性 保證數據唯一

 

方式1:

生產者在投遞消息的時候,生成一個全局唯一id,放在我們消息中。

Msg id=123456

 

Msg id=123456

Msg id=123456

 

消費者獲取到我們該消息,可以根據該全局唯一id實現去重複。

全局唯一id 根據業務來定的  訂單號碼作爲全局的id

實際上還是需要再db層面解決數據防重複。

業務邏輯是在做insert操作 使用唯一主鍵約束

業務邏輯是在做update操作 使用樂觀鎖

  1. 當消費者業務邏輯代碼中,拋出異常自動實現重試 (默認是無數次重試)
  2. 應該對RabbitMQ重試次數實現限制,比如最多重試5次,每次間隔3s;重試多次還是失敗的情況下,存放到死信隊列或者存放到數據庫表中記錄後期人工補償
  3. 消費者獲取消息後,調用第三方接口,但是調用第三方接口失敗呢?是否需要重試 ?
  4. 消費者獲取消息後,應該代碼問題拋出數據異常,是否需要重試?

如何合理選擇消息重試

總結:如果消費者處理消息時,因爲代碼原因拋出異常是需要從新發布版本才能解決的,那麼就不需要重試,重試也解決不了該問題的。存放到死信隊列或者是數據庫表記錄、後期人工實現補償。

 詳細的策略解決冪等性和保證冪等性的方法

https://www.cnblogs.com/javalyy/p/8882144.html

 

 

 

 

 

 

 

 

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