RabbitMQ之訂閱模式與主題模式,消息確認機制

1.訂閱模式

作用類似與微信公衆號,你訂閱了就可以接收到消息

在這裏插入圖片描述解讀:
1.一個生產者,多個消費者。
2.每一個消費者 都有自己的隊列
3.生產者沒有直摟把洧息發送到隊列而是發到了交換機 轉發器exchange
4.每個隊列都要綁定到交換機上。
5.生產者發送的消息經過交換機到達臥列 就能實現一個消息到多個消費者消費

1.2分發模式fanout

該模式下,生產者發送消息,多個消費者通過他們對應的隊列獲得消息,但是所獲得的消息是一樣的,不能指定將哪個消息給那個隊列

創建一個生產者

public class send {
    private static final String EXCHANGE_NAME="MXH_Exchange";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();

        Channel channel = connection.createChannel();
        ----聲明一個交換機
        ----fanout:分發模式
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");

        String msg = "hello exchange";
        發送消息到交換機
        channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());

        channel.close();
        connection.close();
    }
}

創建第一個消費者

public class receive1 {
   private static final String EXCHANGE_NAME="MXH_Exchange";
   private static final String QUEUE_NAME="MXH_Exchange_note";
   public static void main(String[] args) throws IOException, TimeoutException {
       Connection connection = ConnectionUtil.getConnection();

       Channel channel = connection.createChannel();
       聲明一個對列
       channel.queueDeclare(QUEUE_NAME,false,false,false,null);
       將隊列綁定到交換機
       channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
       一次應答前只發送一次
       channel.basicQos(1);

       DefaultConsumer consumer = new DefaultConsumer(channel)
       {
           @Override
           public void handleDelivery(String consumerTag, Envelope envelope,
                                      AMQP.BasicProperties properties,
                                      byte[] body) throws IOException
           {
               String msg = new String(body);
               System.out.println("RE:"+msg);
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e)
               {
                   e.printStackTrace();
               }finally
               {
                   //手動應答
                   channel.basicAck(envelope.getDeliveryTag(),false);
               }
           }

       };
       boolean autoAck = false;//自動應答 true爲開啓 false爲關閉
       channel.basicConsume(QUEUE_NAME,autoAck, consumer);

   }
}

創建第二個消費者

public class receiver2
{
    private static final String EXCHANGE_NAME="MXH_Exchange";
    private static final String QUEUE_NAME="MXH_Exchange_emain";
    public static void main(String[] args) throws IOException, TimeoutException
    {
        ------創建一個連接
        Connection connection = ConnectionUtil.getConnection();
        ------創建一個通道
        Channel channel = connection.createChannel();
        ------創建一個隊列,用戶名
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        ------將隊列綁定到交換機
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
        ------一次應答前只發送一次
        channel.basicQos(1);
        DefaultConsumer consumer = new DefaultConsumer(channel)
        {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException
            {
                String msg = new String(body);
                System.out.println("RE:"+msg);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }finally
                {
                    ------手動應答
                    channel.basicAck(envelope.getDeliveryTag(),false);

                }
            }
        };
        ------自動應答 true爲開啓 false爲關閉
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME,autoAck, consumer);
    }
}

2.Exchange交換機

作用:一方面是接收生產者的消息,另一方面是向隊列推送消息。

2.1Fanout(不處理路由健)

匿名轉發。
Fanout(不處理路由健):對消息不作處理,消息不能指定發給特定的消費者

2.2Direct路由模式

交換機給隊列發送消息時會在後面加一個Key,只有隊列的Key和交換機的Key對應上,交換機纔會把消息發給隊列,如下圖,交換機有error,info,warng三個Key,error的key對應c1和c2,所以帶有error的key的消息就會發送給c1和c2
在這裏插入圖片描述
創建一個生產者
綁定key到交換機

public class send {
    private static final String EXCHANGE_NAME="MXH_Exchange";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();

        Channel channel = connection.createChannel();
        -----聲明一個交換機
      -----fanout:分發模式  channel.exchangeDeclare(EXCHANGE_NAME,"direct");

        String msg = "hello direct";
        -----發送消息到交換機,綁定key到交換機
        String key ="love";
        channel.basicPublish(EXCHANGE_NAME,key,null,msg.getBytes());

        channel.close();
        connection.close();
    }
}

創建第一個消費者
綁定key到交換機

public class receive1 {
    private static final String EXCHANGE_NAME="MXH_Exchange";
    private static final String QUEUE_NAME="MXH_Exchange_note";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();

        Channel channel = connection.createChannel();
        //聲明一個對列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //將隊列綁定到交換機,綁定key到交換機
        String key ="error";
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key);
        //一次應答前只發送一次
        channel.basicQos(1);

        DefaultConsumer consumer = new DefaultConsumer(channel)
        {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException
            {
                String msg = new String(body);
                System.out.println("RE:"+msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }finally
                {
                    //手動應答
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }

        };
        boolean autoAck = false;//自動應答 true爲開啓 false爲關閉
        channel.basicConsume(QUEUE_NAME,autoAck, consumer);

    }
}

創建第二個消費者

public class receiver2
{
    private static final String EXCHANGE_NAME="MXH_Exchange";
    private static final String QUEUE_NAME="MXH_Exchange_emain";
    public static void main(String[] args) throws IOException, TimeoutException
    {
        //創建一個連接
        Connection connection = ConnectionUtil.getConnection();
        //創建一個通道
        Channel channel = connection.createChannel();
        //創建一個隊列,用戶名
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //將隊列綁定到交換機,綁定key到交換機
        String key1 = "error";
        String key2 = "info";
        String key3 = "love";
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key1);
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key2);
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key3);
        //一次應答前只發送一次
        channel.basicQos(1);
        DefaultConsumer consumer = new DefaultConsumer(channel)
        {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException
            {
                String msg = new String(body);
                System.out.println("RE:"+msg);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }finally
                {
                    //手動應答
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        boolean autoAck = false;//自動應答 true爲開啓 false爲關閉
        channel.basicConsume(QUEUE_NAME,autoAck, consumer);
    }
}

2.3Toplc exchanger主題模式

Toplc exchanger:將路由鍵和某模式匹配,
#匹配一個或者多個。
‘*’匹配一個。

第二個消費者:
注意我的key值: String key =“love.#”;
#代表多個,只要生產者發給交換機的key是love開頭,這個消費者就能接收到
在這裏插入圖片描述在這裏插入圖片描述
創建一個生產者:
注意我的key值:String key =“love.del”;

public class send {
    private static final String EXCHANGE_NAME="MXH_Exchange_topic";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();

        Channel channel = connection.createChannel();
        //聲明一個交換機
        channel.exchangeDeclare(EXCHANGE_NAME,"topic");//fanout:分發模式

        String msg = "hello direct";
        //發送消息到交換機
        String key ="love.del";
        channel.basicPublish(EXCHANGE_NAME,key,null,msg.getBytes());

        channel.close();
        connection.close();
    }
}

創建第一個消費者
注意我的key值: String key =“love.add”;

public class receive1 {
    private static final String EXCHANGE_NAME="MXH_Exchange_topic";
    private static final String QUEUE_NAME="MXH_Exchange_note";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();

        Channel channel = connection.createChannel();
        //聲明一個對列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //將隊列綁定到交換機
        String key ="love.add";
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key);
        //一次應答前只發送一次
        channel.basicQos(1);

        DefaultConsumer consumer = new DefaultConsumer(channel)
        {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException
            {
                String msg = new String(body);
                System.out.println("RE:"+msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }finally
                {
                    //手動應答
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }

        };
        boolean autoAck = false;//自動應答 true爲開啓 false爲關閉
        channel.basicConsume(QUEUE_NAME,autoAck, consumer);

    }
}

創建第二個消費者:
注意我的key值: String key =“love.#”;
#代表多個,只要生產者發給交換機的key是love開頭,這個消費者就能接收到

public class receiver2
{
    private static final String EXCHANGE_NAME="MXH_Exchange_topic";
    private static final String QUEUE_NAME="MXH_Exchange_emain";
    public static void main(String[] args) throws IOException, TimeoutException
    {
        //創建一個連接
        Connection connection = ConnectionUtil.getConnection();
        //創建一個通道
        Channel channel = connection.createChannel();
        //創建一個隊列,用戶名
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //將隊列綁定到交換機
        String key = "love.#";
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key);
        //一次應答前只發送一次
        channel.basicQos(1);
        DefaultConsumer consumer = new DefaultConsumer(channel)
        {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException
            {
                String msg = new String(body);
                System.out.println("RE:"+msg);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }finally
                {
                    //手動應答
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        boolean autoAck = false;//自動應答 true爲開啓 false爲關閉
        channel.basicConsume(QUEUE_NAME,autoAck, consumer);
    }
}

消息確認機制

在rabbitmq中我們可以通過持久化數據解決rabbitmq服務器異常的數據丟失問題。

問題:生產者將消息發送出去之後,消息到底有沒有到達rabbitmq 服務器默認的情況是不知道的;

兩種方式可以知道:
1.AMQP實現的事務機制
2.Confirm橫式

AMQP實現的事務機制

AMQP提供了三個方法
channel.txSelect();-----開啓事物:在消息發送前開啓
channel.txCommit();-----提交事物:在消息發送後提交
channel.txRollback();-----事物回滾:如果有異常就回滾,處理異常

這種模式增加了服務器的請求量,使服務器處理的效率降低

public class send {
    private static final String QUEUE_NAME="MXH";
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //創建一個連接
        Connection connection = ConnectionUtil.getConnection();
        //從連接中獲取一個通道
        Channel channel = connection.createChannel();
        //創建一個消息隊列  QUEUE_NAME:隊列名
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

            try
            {


            String msg = "hello,rqbbitmq你好";
            channel.txSelect();-----開啓事物
            channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
            int i =1/0;
            channel.txCommit();-----提交事物
            }catch (Exception e)
            {
                channel.txRollback();-----事物回滾
                System.out.println("發送異常,消息未送達");
            }

        channel.close();
        connection.close();
    }
}

Confirm橫式

原理

生產者將信道設置成confirm橫式,一旦信道進入confirm橫式,所有在該信道上面發佈的洧息都會被指很一個唯一的ID(從1開始)。一旦洧息被投遞到所有匹配的隊列之後,broker就會發送-一個確認給生產者(包含消息的唯一ID) .這就使得生產者知道消息已經正確到達目的隊列了。如果消息和隊列是可持久化的,那麼確認消息會將消息寫入磁盤之後發出,broker 固傳給生產者的確認消息中deliver-tag 域包含了確認消息的序列號,此外broker也可以設置basic.ack 的multiple城,表示到這個序列號之前的所有消息都已經得到了處理。。

Confirm摸式最大的好處在於他是異步。

串行模式

channel.confirmSelect();//開啓confirm模式
channel.waitForConfirms():該方法在生產者發送消息到隊列後會返回true
if(channel.waitForConfirms())
{
System.out.println(“發送成功”);
}else
{
System.out.println(“發送失敗”);
}

普通模式:該模式一次發送一條消息到隊列,一發一回

public class send {
    private static final String QUEUE_NAME="MXH_confirm";
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //創建一個連接
        Connection connection = ConnectionUtil.getConnection();
        //從連接中獲取一個通道
        Channel channel = connection.createChannel();
        //創建一個消息隊列  QUEUE_NAME:隊列名
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        try {

          channel.confirmSelect();//開啓confirm模式
            String msg = "hello,rqbbitmq你好";
            int i =1/0;
            channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
            if(channel.waitForConfirms())
            {
                System.out.println("發送成功");
            }else
            {
                System.out.println("發送失敗");
            }
        }catch (Exception e)
        {
            System.out.println("發送失敗");
        }

        channel.close();
        connection.close();
    }
}

批量模式:該模式用for循環發送多條數據,多發一回,發完所有的消息服務器回一條是否收到消息

public class send {
    private static final String QUEUE_NAME="MXH_confirm";
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //創建一個連接
        Connection connection = ConnectionUtil.getConnection();
        //從連接中獲取一個通道
        Channel channel = connection.createChannel();
        //創建一個消息隊列  QUEUE_NAME:隊列名
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        try {

          channel.confirmSelect();//開啓confirm模式
            String msg = "hello,rqbbitmq你好";
            int i =1/0;
            for(int p =0;p<10;p++)
            {
                channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
            }

            if(channel.waitForConfirms())
            {
                System.out.println("發送成功");
            }else
            {
                System.out.println("發送失敗");
            }
        }catch (Exception e)
        {
            System.out.println("發送失敗");
        }

        channel.close();
        connection.close();
    }
}

異步模式

原理

Channel對象提供的Confirmlistener回調方法只包含deliveryTag (當前Chanel發出的消息序號)。我們需要自己爲每一個Channel維護一個unconfirm的消息序號集合,每publish 一條數據,集合中元素加1.每回調一次handleAck方法,unconfirm 集合刪掉相應的一條(multiple=false)或多條(multiple=true)記錄。從程序運行效率上看,這個unconfirm集合最好採用有序集合SortedSet存儲結構。

創建一個生產者:

public class send {
    private static final String QUEUE_NAME="MXH_confirm";
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException
    {
        //創建一個連接
        Connection connection = ConnectionUtil.getConnection();
        //從連接中獲取一個通道
        Channel channel = connection.createChannel();
        //創建一個消息隊列  QUEUE_NAME:隊列名
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

            channel.confirmSelect();//開啓confirm模式
            ----未確認的消息標識
            final SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
            ----添加通道監聽
            channel.addConfirmListener(new ConfirmListener()
            {
                @Override
               -----消息發送成功
                public void handleAck(long deliveryTag , boolean mulpitle) throws IOException
                {
                    if (mulpitle) {
                        confirmSet.headSet(deliveryTag  + 1).clear();
                    } else {
                        confirmSet.remove(deliveryTag );
                    }
                }

                @Override
                -----消息發送失敗
                public void handleNack(long deliveryTag , boolean mulpitle) throws IOException {
                    if (mulpitle) {
                        confirmSet.headSet(deliveryTag  + 1).clear();
                    } else {
                        confirmSet.remove(deliveryTag );
                    }
                }
            });

            String msg = "hello,rqbbitmq你好";
        while(true)
        {
            long setNo = channel.getNextPublishSeqNo();
            channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
            confirmSet.add(setNo);
        }
//        channel.close();
//        connection.close();
    }
}

spring整合rabbitMQ

在這裏插入圖片描述在這裏插入圖片描述

在這裏插入圖片描述

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