rabbitmq基礎(chǔ)-工作模式

本文大量內(nèi)容來之網(wǎng)絡(luò),如有侵權(quán)聯(lián)系我

4. AMQP

4.1. 相關(guān)概念介紹

AMQP 一個提供統(tǒng)一消息服務(wù)的應(yīng)用層標準高級消息隊列協(xié)議,是應(yīng)用層協(xié)議的一個開放標準,為面向消息的中間件設(shè)計。

AMQP是一個二進制協(xié)議,擁有一些現(xiàn)代化特點:多信道、協(xié)商式,異步,安全,擴平臺,中立,高效。

RabbitMQ是AMQP協(xié)議的Erlang的實現(xiàn)。

概念 說明
連接Connection 一個網(wǎng)絡(luò)連接,比如TCP/IP套接字連接。
會話Session 端點之間的命名對話。在一個會話上下文中,保證“恰好傳遞一次”。
信道Channel 多路復(fù)用連接中的一條獨立的雙向數(shù)據(jù)流通道。為會話提供物理傳輸介質(zhì)。
客戶端Client AMQP連接或者會話的發(fā)起者。AMQP是非對稱的,客戶端生產(chǎn)和消費消息,服務(wù)器存儲和路由這些消息。
服務(wù)節(jié)點Broker 消息中間件的服務(wù)節(jié)點;一般情況下可以將一個RabbitMQ Broker看作一臺RabbitMQ 服務(wù)器。
端點 AMQP對話的任意一方。一個AMQP連接包括兩個端點(一個是客戶端,一個是服務(wù)器)。
消費者Consumer 一個從消息隊列里請求消息的客戶端程序。
生產(chǎn)者Producer 一個向交換機發(fā)布消息的客戶端應(yīng)用程序。

1.4. RabbitMQ

RabbitMQ是由erlang語言開發(fā),基于AMQP(Advanced Message Queue 高級消息隊列協(xié)議)協(xié)議實現(xiàn)的消息隊列,它是一種應(yīng)用程序之間的通信方法,消息隊列在分布式系統(tǒng)開發(fā)中應(yīng)用非常廣泛。

RabbitMQ官方地址:http://www.rabbitmq.com/

RabbitMQ提供了6種模式:簡單模式,work模式,Publish/Subscribe發(fā)布與訂閱模式,Routing路由模式,Topics主題模式,RPC遠程調(diào)用模式(遠程調(diào)用,不太算MQ;暫不作介紹);

官網(wǎng)對應(yīng)模式介紹:https://www.rabbitmq.com/getstarted.html

image.png

image.png

3.1.2. 添加依賴

往heima-rabbitmq的pom.xml文件中添加如下依賴:

        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.6.0</version>
        </dependency>

在基礎(chǔ)文章中我會先用代碼展示所有常用api,在整個文集的最后會附加我github封裝,學(xué)習(xí)都是一步步來的,先看看基礎(chǔ)api

1、簡單模式


image.png

在上圖的模型中,有以下概念:

  • P:生產(chǎn)者,也就是要發(fā)送消息的程序
  • C:消費者:消息的接受者,會一直等待消息到來。
  • queue:消息隊列,圖中紅色部分。類似一個郵箱,可以緩存消息;生產(chǎn)者向其中投遞消息,消費者從其中取出消息。

生產(chǎn)者代碼Producer

 package com.itheima.rabbitmq.simple;

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

public class Producer {

    static final String QUEUE_NAME = "simple_queue";

    public static void main(String[] args) throws Exception {
        //創(chuàng)建連接工廠
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //主機地址;默認為 localhost
        connectionFactory.setHost("localhost");
        //連接端口;默認為 5672
        connectionFactory.setPort(5672);
        //虛擬主機名稱;默認為 /
        connectionFactory.setVirtualHost("/cast");
        //連接用戶名;默認為guest
        connectionFactory.setUsername("guest");
        //連接密碼;默認為guest
        connectionFactory.setPassword("guest");

        //創(chuàng)建連接
        Connection connection = connectionFactory.newConnection();

        // 創(chuàng)建頻道
        Channel channel = connection.createChannel();

        // 聲明(創(chuàng)建)隊列
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否定義持久化隊列
         * 參數(shù)3:是否獨占本次連接
         * 參數(shù)4:是否在不使用的時候自動刪除隊列
         * 參數(shù)5:隊列其它參數(shù)
         */
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);

        // 要發(fā)送的信息
        String message = "你好, 小美妞!";
        /**
         * 參數(shù)1:交換機名稱,如果沒有指定則使用默認Default Exchage
         * 參數(shù)2:路由key,簡單模式可以傳遞隊列名稱
         * 參數(shù)3:消息其它屬性
         * 參數(shù)4:消息內(nèi)容
         */
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
        System.out.println("已發(fā)送消息:" + message);

        // 關(guān)閉資源
        channel.close();
        connection.close();
    }
}

在生產(chǎn)者代碼運行起來后開在控制臺查看的


image.png

編寫消費者代碼Consumer

package com.itheima.rabbitmq.simple;

import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer {

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();

        // 創(chuàng)建頻道
        Channel channel = connection.createChannel();

        // 聲明(創(chuàng)建)隊列
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否定義持久化隊列
         * 參數(shù)3:是否獨占本次連接
         * 參數(shù)4:是否在不使用的時候自動刪除隊列
         * 參數(shù)5:隊列其它參數(shù)
         */
        channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);

        //創(chuàng)建消費者;并設(shè)置消息處理
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            /**
             * consumerTag 消息者標簽,在channel.basicConsume時候可以指定
             * envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發(fā)送)
             * properties 屬性信息
             * body 消息
             */
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key為:" + envelope.getRoutingKey());
                //交換機
                System.out.println("交換機為:" + envelope.getExchange());
                //消息id
                System.out.println("消息id為:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("接收到的消息為:" + new String(body, "utf-8"));
            }
        };
        //監(jiān)聽消息
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否自動確認,設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動確認
         * 參數(shù)3:消息接收到后回調(diào)
         */
        channel.basicConsume(Producer.QUEUE_NAME, true, consumer);

        //不關(guān)閉資源,應(yīng)該一直監(jiān)聽消息
        //channel.close();
        //connection.close();
    }
}

4.2. RabbitMQ運轉(zhuǎn)流程

在上述代碼中:

  • 生產(chǎn)者發(fā)送消息
    1. 生產(chǎn)者創(chuàng)建連接(Connection),開啟一個信道(Channel),連接到RabbitMQ Broker;
    2. 聲明隊列并設(shè)置屬性;如是否排它,是否持久化,是否自動刪除;
    3. 將路由鍵(空字符串)與隊列綁定起來;
    4. 發(fā)送消息至RabbitMQ Broker;
    5. 關(guān)閉信道;
    6. 關(guān)閉連接;
  • 消費者接收消息
    1. 消費者創(chuàng)建連接(Connection),開啟一個信道(Channel),連接到RabbitMQ Broker
    2. 向Broker 請求消費相應(yīng)隊列中的消息,設(shè)置相應(yīng)的回調(diào)函數(shù);
    3. 等待Broker回應(yīng)閉關(guān)投遞響應(yīng)隊列中的消息,消費者接收消息;
    4. 確認(ack,自動確認)接收到的消息;
    5. RabbitMQ從隊列中刪除相應(yīng)已經(jīng)被確認的消息;
    6. 關(guān)閉信道;
    7. 關(guān)閉連接;


      image.png

4.3. 生產(chǎn)者流轉(zhuǎn)過程說明

  1. 客戶端與代理服務(wù)器Broker建立連接。會調(diào)用newConnection() 方法,這個方法會進一步封裝Protocol Header 0-9-1 的報文頭發(fā)送給Broker ,以此通知Broker 本次交互采用的是AMQPO-9-1 協(xié)議,緊接著Broker 返回Connection.Start 來建立連接,在連接的過程中涉及Connection.Start/.Start-OK 、Connection.Tune/.Tune-Ok ,Connection.Open/ .Open-Ok 這6 個命令的交互。
  2. 客戶端調(diào)用connection.createChannel方法。此方法開啟信道,其包裝的channel.open命令發(fā)送給Broker,等待channel.basicPublish方法,對應(yīng)的AMQP命令為Basic.Publish,這個命令包含了content Header 和content Body()。content Header 包含了消息體的屬性,例如:投遞模式,優(yōu)先級等,content Body 包含了消息體本身。
  3. 客戶端發(fā)送完消息需要關(guān)閉資源時,涉及到Channel.Close和Channl.Close-Ok 與Connetion.Close和Connection.Close-Ok的命令交互。


    image.png

4.4. 消費者流轉(zhuǎn)過程說明

  1. 消費者客戶端與代理服務(wù)器Broker建立連接。會調(diào)用newConnection() 方法,這個方法會進一步封裝Protocol Header 0-9-1 的報文頭發(fā)送給Broker ,以此通知Broker 本次交互采用的是AMQPO-9-1 協(xié)議,緊接著Broker 返回Connection.Start 來建立連接,在連接的過程中涉及Connection.Start/.Start-OK 、Connection.Tune/.Tune-Ok ,Connection.Open/ .Open-Ok 這6 個命令的交互。
  2. 消費者客戶端調(diào)用connection.createChannel方法。和生產(chǎn)者客戶端一樣,協(xié)議涉及Channel . Open/Open-Ok命令。
  3. 在真正消費之前,消費者客戶端需要向Broker 發(fā)送Basic.Consume 命令(即調(diào)用channel.basicConsume 方法〉將Channel 置為接收模式,之后Broker 回執(zhí)Basic . Consume - Ok 以告訴消費者客戶端準備好消費消息。
  4. Broker 向消費者客戶端推送(Push) 消息,即Basic.Deliver 命令,這個命令和Basic.Publish 命令一樣會攜帶Content Header 和Content Body。
  5. 消費者接收到消息并正確消費之后,向Broker 發(fā)送確認,即Basic.Ack 命令。
  6. 客戶端發(fā)送完消息需要關(guān)閉資源時,涉及到Channel.Close和Channl.Close-Ok 與Connetion.Close和Connection.Close-Ok的命令交互。

[圖片上傳失敗...(image-cbb769-1612271105479)]


image.png

Work queues工作隊列模式

image.png

Work Queues與入門程序的簡單模式相比,多了一個或一些消費端,多個消費端共同消費同一個隊列中的消息。

應(yīng)用場景:對于 任務(wù)過重或任務(wù)較多情況使用工作隊列可以提高任務(wù)處理的速度。

Work Queues與入門程序的簡單模式的代碼是幾乎一樣的;可以完全復(fù)制,并復(fù)制多一個消費者進行多個消費者同時消費消息的測試。

package com.itheima.rabbitmq.work;

import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Producer {

    static final String QUEUE_NAME = "work_queue";

    public static void main(String[] args) throws Exception {

        //創(chuàng)建連接
        Connection connection = ConnectionUtil.getConnection();

        // 創(chuàng)建頻道
        Channel channel = connection.createChannel();

        // 聲明(創(chuàng)建)隊列
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否定義持久化隊列
         * 參數(shù)3:是否獨占本次連接
         * 參數(shù)4:是否在不使用的時候自動刪除隊列
         * 參數(shù)5:隊列其它參數(shù)
         */
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);

        for (int i = 1; i <= 30; i++) {
            // 發(fā)送信息
            String message = "你好,小美妞 work模式--" + I;
            /**
             * 參數(shù)1:交換機名稱,如果沒有指定則使用默認Default Exchage
             * 參數(shù)2:路由key,簡單模式可以傳遞隊列名稱
             * 參數(shù)3:消息其它屬性
             * 參數(shù)4:消息內(nèi)容
             */
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            System.out.println("已發(fā)送消息:" + message);
        }

        // 關(guān)閉資源
        channel.close();
        connection.close();
    }
}
package com.itheima.rabbitmq.work;

import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer1 {

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();

        // 創(chuàng)建頻道
        Channel channel = connection.createChannel();

        // 聲明(創(chuàng)建)隊列
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否定義持久化隊列
         * 參數(shù)3:是否獨占本次連接
         * 參數(shù)4:是否在不使用的時候自動刪除隊列
         * 參數(shù)5:隊列其它參數(shù)
         */
        channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);

        //一次只能接收并處理一個消息
        channel.basicQos(1);

        //創(chuàng)建消費者;并設(shè)置消息處理
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            /**
             * consumerTag 消息者標簽,在channel.basicConsume時候可以指定
             * envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發(fā)送)
             * properties 屬性信息
             * body 消息
             */
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    //路由key
                    System.out.println("路由key為:" + envelope.getRoutingKey());
                    //交換機
                    System.out.println("交換機為:" + envelope.getExchange());
                    //消息id
                    System.out.println("消息id為:" + envelope.getDeliveryTag());
                    //收到的消息
                    System.out.println("消費者1-接收到的消息為:" + new String(body, "utf-8"));
                    Thread.sleep(1000);

                    //確認消息
                    channel.basicAck(envelope.getDeliveryTag(), false);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        //監(jiān)聽消息
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否自動確認,設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動確認
         * 參數(shù)3:消息接收到后回調(diào)
         */
        channel.basicConsume(Producer.QUEUE_NAME, false, consumer);
    }
}
package com.itheima.rabbitmq.work;

import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer2 {

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();

        // 創(chuàng)建頻道
        Channel channel = connection.createChannel();

        // 聲明(創(chuàng)建)隊列
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否定義持久化隊列
         * 參數(shù)3:是否獨占本次連接
         * 參數(shù)4:是否在不使用的時候自動刪除隊列
         * 參數(shù)5:隊列其它參數(shù)
         */
        channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);

        //一次只能接收并處理一個消息
        channel.basicQos(1);

        //創(chuàng)建消費者;并設(shè)置消息處理
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            /**
             * consumerTag 消息者標簽,在channel.basicConsume時候可以指定
             * envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發(fā)送)
             * properties 屬性信息
             * body 消息
             */
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    //路由key
                    System.out.println("路由key為:" + envelope.getRoutingKey());
                    //交換機
                    System.out.println("交換機為:" + envelope.getExchange());
                    //消息id
                    System.out.println("消息id為:" + envelope.getDeliveryTag());
                    //收到的消息
                    System.out.println("消費者2-接收到的消息為:" + new String(body, "utf-8"));
                    Thread.sleep(1000);

                    //確認消息
                    channel.basicAck(envelope.getDeliveryTag(), false);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        //監(jiān)聽消息
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否自動確認,設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動確認
         * 參數(shù)3:消息接收到后回調(diào)
         */
        channel.basicConsume(Producer.QUEUE_NAME, false, consumer);
    }
}

小結(jié)

在一個隊列中如果有多個消費者,那么消費者之間對于同一個消息的關(guān)系是競爭的關(guān)系。

訂閱模式類型

image.png

前面2個案例中,只有3個角色:

  • P:生產(chǎn)者,也就是要發(fā)送消息的程序
  • C:消費者:消息的接受者,會一直等待消息到來。
  • queue:消息隊列,圖中紅色部分

而在訂閱模型中,多了一個exchange角色,而且過程略有變化:

  • P:生產(chǎn)者,也就是要發(fā)送消息的程序,但是不再發(fā)送到隊列中,而是發(fā)給X(交換機)
  • C:消費者,消息的接受者,會一直等待消息到來。
  • Queue:消息隊列,接收消息、緩存消息。
  • Exchange:交換機,圖中的X。一方面,接收生產(chǎn)者發(fā)送的消息。另一方面,知道如何處理消息,例如遞交給某個特別隊列、遞交給所有隊列、或是將消息丟棄。到底如何操作,取決于Exchange的類型。Exchange有常見以下3種類型:
    • Fanout:廣播,將消息交給所有綁定到交換機的隊列
    • Direct:定向,把消息交給符合指定routing key 的隊列
    • Topic:通配符,把消息交給符合routing pattern(路由模式) 的隊列

Exchange(交換機)只負責轉(zhuǎn)發(fā)消息,不具備存儲消息的能力,因此如果沒有任何隊列與Exchange綁定,或者沒有符合路由規(guī)則的隊列,那么消息會丟失!
producer

package com.itheima.rabbitmq.ps;

import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

/**
 * 發(fā)布與訂閱使用的交換機類型為:fanout
 */
public class Producer {

    //交換機名稱
    static final String FANOUT_EXCHAGE = "fanout_exchange";
    //隊列名稱
    static final String FANOUT_QUEUE_1 = "fanout_queue_1";
    //隊列名稱
    static final String FANOUT_QUEUE_2 = "fanout_queue_2";

    public static void main(String[] args) throws Exception {

        //創(chuàng)建連接
        Connection connection = ConnectionUtil.getConnection();

        // 創(chuàng)建頻道
        Channel channel = connection.createChannel();

        /**
         * 聲明交換機
         * 參數(shù)1:交換機名稱
         * 參數(shù)2:交換機類型,fanout、topic、direct、headers
         */
        channel.exchangeDeclare(FANOUT_EXCHAGE, BuiltinExchangeType.FANOUT);

        // 聲明(創(chuàng)建)隊列
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否定義持久化隊列
         * 參數(shù)3:是否獨占本次連接
         * 參數(shù)4:是否在不使用的時候自動刪除隊列
         * 參數(shù)5:隊列其它參數(shù)
         */
        channel.queueDeclare(FANOUT_QUEUE_1, true, false, false, null);
        channel.queueDeclare(FANOUT_QUEUE_2, true, false, false, null);

        //隊列綁定交換機
        channel.queueBind(FANOUT_QUEUE_1, FANOUT_EXCHAGE, "");
        channel.queueBind(FANOUT_QUEUE_2, FANOUT_EXCHAGE, "");

        for (int i = 1; i <= 10; i++) {
            // 發(fā)送信息
            String message = "你好;小兔子!發(fā)布訂閱模式--" + I;
            /**
             * 參數(shù)1:交換機名稱,如果沒有指定則使用默認Default Exchage
             * 參數(shù)2:路由key,簡單模式可以傳遞隊列名稱
             * 參數(shù)3:消息其它屬性
             * 參數(shù)4:消息內(nèi)容
             */
            channel.basicPublish(FANOUT_EXCHAGE, "", null, message.getBytes());
            System.out.println("已發(fā)送消息:" + message);
        }

        // 關(guān)閉資源
        channel.close();
        connection.close();
    }
}

consumer1

package com.itheima.rabbitmq.ps;

import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer1 {

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();

        // 創(chuàng)建頻道
        Channel channel = connection.createChannel();

        //聲明交換機
        channel.exchangeDeclare(Producer.FANOUT_EXCHAGE, BuiltinExchangeType.FANOUT);

        // 聲明(創(chuàng)建)隊列
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否定義持久化隊列
         * 參數(shù)3:是否獨占本次連接
         * 參數(shù)4:是否在不使用的時候自動刪除隊列
         * 參數(shù)5:隊列其它參數(shù)
         */
        channel.queueDeclare(Producer.FANOUT_QUEUE_1, true, false, false, null);

        //隊列綁定交換機
        channel.queueBind(Producer.FANOUT_QUEUE_1, Producer.FANOUT_EXCHAGE, "");

        //創(chuàng)建消費者;并設(shè)置消息處理
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            /**
             * consumerTag 消息者標簽,在channel.basicConsume時候可以指定
             * envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發(fā)送)
             * properties 屬性信息
             * body 消息
             */
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key為:" + envelope.getRoutingKey());
                //交換機
                System.out.println("交換機為:" + envelope.getExchange());
                //消息id
                System.out.println("消息id為:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("消費者1-接收到的消息為:" + new String(body, "utf-8"));
            }
        };
        //監(jiān)聽消息
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否自動確認,設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動確認
         * 參數(shù)3:消息接收到后回調(diào)
         */
        channel.basicConsume(Producer.FANOUT_QUEUE_1, true, consumer);
    }
}

comsumer2

package com.itheima.rabbitmq.ps;

import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer2 {

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();

        // 創(chuàng)建頻道
        Channel channel = connection.createChannel();

        //聲明交換機
        channel.exchangeDeclare(Producer.FANOUT_EXCHAGE, BuiltinExchangeType.FANOUT);

        // 聲明(創(chuàng)建)隊列
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否定義持久化隊列
         * 參數(shù)3:是否獨占本次連接
         * 參數(shù)4:是否在不使用的時候自動刪除隊列
         * 參數(shù)5:隊列其它參數(shù)
         */
        channel.queueDeclare(Producer.FANOUT_QUEUE_2, true, false, false, null);

        //隊列綁定交換機
        channel.queueBind(Producer.FANOUT_QUEUE_2, Producer.FANOUT_EXCHAGE, "");

        //創(chuàng)建消費者;并設(shè)置消息處理
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            /**
             * consumerTag 消息者標簽,在channel.basicConsume時候可以指定
             * envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發(fā)送)
             * properties 屬性信息
             * body 消息
             */
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key為:" + envelope.getRoutingKey());
                //交換機
                System.out.println("交換機為:" + envelope.getExchange());
                //消息id
                System.out.println("消息id為:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("消費者2-接收到的消息為:" + new String(body, "utf-8"));
            }
        };
        //監(jiān)聽消息
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否自動確認,設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動確認
         * 參數(shù)3:消息接收到后回調(diào)
         */
        channel.basicConsume(Producer.FANOUT_QUEUE_2, true, consumer);
    }
}

啟動所有消費者,然后使用生產(chǎn)者發(fā)送消息;在每個消費者對應(yīng)的控制臺可以查看到生產(chǎn)者發(fā)送的所有消息;到達廣播的效果。

4.3.4. 小結(jié)

交換機需要與隊列進行綁定,綁定之后;一個消息可以被多個消費者都收到。

發(fā)布訂閱模式與工作隊列模式的區(qū)別

1、工作隊列模式不用定義交換機,而發(fā)布/訂閱模式需要定義交換機。

2、發(fā)布/訂閱模式的生產(chǎn)方是面向交換機發(fā)送消息,工作隊列模式的生產(chǎn)方是面向隊列發(fā)送消息(底層使用默認交換機)。

3、發(fā)布/訂閱模式需要設(shè)置隊列和交換機的綁定,工作隊列模式不需要設(shè)置,實際上工作隊列模式會將隊列綁 定到默認的交換機 。

4.4. Routing路由模式

4.4.1. 模式說明

路由模式特點:

  • 隊列與交換機的綁定,不能是任意綁定了,而是要指定一個RoutingKey(路由key)

  • 消息的發(fā)送方在 向 Exchange發(fā)送消息時,也必須指定消息的 RoutingKey

  • Exchange不再把消息交給每一個綁定的隊列,而是根據(jù)消息的Routing Key進行判斷,只有隊列的Routingkey與消息的 Routing key完全一致,才會接收到消息

image.png
  • P:生產(chǎn)者,向Exchange發(fā)送消息,發(fā)送消息時,會指定一個routing key。

  • X:Exchange(交換機),接收生產(chǎn)者的消息,然后把消息遞交給 與routing key完全匹配的隊列

  • C1:消費者,其所在隊列指定了需要routing key 為 error 的消息

  • C2:消費者,其所在隊列指定了需要routing key 為 info、error、warning 的消息

代碼

在編碼上與 Publish/Subscribe發(fā)布與訂閱模式 的區(qū)別是交換機的類型為:Direct,還有隊列綁定交換機的時候需要指定routing key。

package com.itheima.rabbitmq.routing;

import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

/**
 * 路由模式的交換機類型為:direct
 */
public class Producer {

    //交換機名稱
    static final String DIRECT_EXCHAGE = "direct_exchange";
    //隊列名稱
    static final String DIRECT_QUEUE_INSERT = "direct_queue_insert";
    //隊列名稱
    static final String DIRECT_QUEUE_UPDATE = "direct_queue_update";

    public static void main(String[] args) throws Exception {

        //創(chuàng)建連接
        Connection connection = ConnectionUtil.getConnection();

        // 創(chuàng)建頻道
        Channel channel = connection.createChannel();

        /**
         * 聲明交換機
         * 參數(shù)1:交換機名稱
         * 參數(shù)2:交換機類型,fanout、topic、direct、headers
         */
        channel.exchangeDeclare(DIRECT_EXCHAGE, BuiltinExchangeType.DIRECT);

        // 聲明(創(chuàng)建)隊列
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否定義持久化隊列
         * 參數(shù)3:是否獨占本次連接
         * 參數(shù)4:是否在不使用的時候自動刪除隊列
         * 參數(shù)5:隊列其它參數(shù)
         */
        channel.queueDeclare(DIRECT_QUEUE_INSERT, true, false, false, null);
        channel.queueDeclare(DIRECT_QUEUE_UPDATE, true, false, false, null);

        //隊列綁定交換機
        channel.queueBind(DIRECT_QUEUE_INSERT, DIRECT_EXCHAGE, "insert");
        channel.queueBind(DIRECT_QUEUE_UPDATE, DIRECT_EXCHAGE, "update");

        // 發(fā)送信息
        String message = "新增了商品。路由模式;routing key 為 insert " ;
        /**
         * 參數(shù)1:交換機名稱,如果沒有指定則使用默認Default Exchage
         * 參數(shù)2:路由key,簡單模式可以傳遞隊列名稱
         * 參數(shù)3:消息其它屬性
         * 參數(shù)4:消息內(nèi)容
         */
        channel.basicPublish(DIRECT_EXCHAGE, "insert", null, message.getBytes());
        System.out.println("已發(fā)送消息:" + message);

        // 發(fā)送信息
        message = "修改了商品。路由模式;routing key 為 update" ;
        /**
         * 參數(shù)1:交換機名稱,如果沒有指定則使用默認Default Exchage
         * 參數(shù)2:路由key,簡單模式可以傳遞隊列名稱
         * 參數(shù)3:消息其它屬性
         * 參數(shù)4:消息內(nèi)容
         */
        channel.basicPublish(DIRECT_EXCHAGE, "update", null, message.getBytes());
        System.out.println("已發(fā)送消息:" + message);

        // 關(guān)閉資源
        channel.close();
        connection.close();
    }
}


package com.itheima.rabbitmq.routing;

import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer1 {

public static void main(String[] args) throws Exception {
    Connection connection = ConnectionUtil.getConnection();

    // 創(chuàng)建頻道
    Channel channel = connection.createChannel();

    //聲明交換機
    channel.exchangeDeclare(Producer.DIRECT_EXCHAGE, BuiltinExchangeType.DIRECT);

    // 聲明(創(chuàng)建)隊列
    /**
     * 參數(shù)1:隊列名稱
     * 參數(shù)2:是否定義持久化隊列
     * 參數(shù)3:是否獨占本次連接
     * 參數(shù)4:是否在不使用的時候自動刪除隊列
     * 參數(shù)5:隊列其它參數(shù)
     */
    channel.queueDeclare(Producer.DIRECT_QUEUE_INSERT, true, false, false, null);

    //隊列綁定交換機
    channel.queueBind(Producer.DIRECT_QUEUE_INSERT, Producer.DIRECT_EXCHAGE, "insert");

    //創(chuàng)建消費者;并設(shè)置消息處理
    DefaultConsumer consumer = new DefaultConsumer(channel){
        @Override
        /**
         * consumerTag 消息者標簽,在channel.basicConsume時候可以指定
         * envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發(fā)送)
         * properties 屬性信息
         * body 消息
         */
        public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
            //路由key
            System.out.println("路由key為:" + envelope.getRoutingKey());
            //交換機
            System.out.println("交換機為:" + envelope.getExchange());
            //消息id
            System.out.println("消息id為:" + envelope.getDeliveryTag());
            //收到的消息
            System.out.println("消費者1-接收到的消息為:" + new String(body, "utf-8"));
        }
    };
    //監(jiān)聽消息
    /**
     * 參數(shù)1:隊列名稱
     * 參數(shù)2:是否自動確認,設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動確認
     * 參數(shù)3:消息接收到后回調(diào)
     */
    channel.basicConsume(Producer.DIRECT_QUEUE_INSERT, true, consumer);
}

}

package com.itheima.rabbitmq.routing;

import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer2 {

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();

        // 創(chuàng)建頻道
        Channel channel = connection.createChannel();

        //聲明交換機
        channel.exchangeDeclare(Producer.DIRECT_EXCHAGE, BuiltinExchangeType.DIRECT);

        // 聲明(創(chuàng)建)隊列
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否定義持久化隊列
         * 參數(shù)3:是否獨占本次連接
         * 參數(shù)4:是否在不使用的時候自動刪除隊列
         * 參數(shù)5:隊列其它參數(shù)
         */
        channel.queueDeclare(Producer.DIRECT_QUEUE_UPDATE, true, false, false, null);

        //隊列綁定交換機
        channel.queueBind(Producer.DIRECT_QUEUE_UPDATE, Producer.DIRECT_EXCHAGE, "update");

        //創(chuàng)建消費者;并設(shè)置消息處理
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            /**
             * consumerTag 消息者標簽,在channel.basicConsume時候可以指定
             * envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發(fā)送)
             * properties 屬性信息
             * body 消息
             */
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key為:" + envelope.getRoutingKey());
                //交換機
                System.out.println("交換機為:" + envelope.getExchange());
                //消息id
                System.out.println("消息id為:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("消費者2-接收到的消息為:" + new String(body, "utf-8"));
            }
        };
        //監(jiān)聽消息
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否自動確認,設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動確認
         * 參數(shù)3:消息接收到后回調(diào)
         */
        channel.basicConsume(Producer.DIRECT_QUEUE_UPDATE, true, consumer);
    }
}

測試

啟動所有消費者,然后使用生產(chǎn)者發(fā)送消息;在消費者對應(yīng)的控制臺可以查看到生產(chǎn)者發(fā)送對應(yīng)routing key對應(yīng)隊列的消息;到達按照需要接收的效果。

在執(zhí)行完測試代碼后,其實到RabbitMQ的管理后臺找到Exchanges選項卡,點擊 direct_exchange 的交換機,可以查看到如下的綁定:

image.png

小結(jié)

Routing模式要求隊列在綁定交換機時要指定routing key,消息會轉(zhuǎn)發(fā)到符合routing key的隊列。

Topics通配符模式

模式說明

Topic類型與Direct相比,都是可以根據(jù)RoutingKey把消息路由到不同的隊列。只不過Topic類型Exchange可以讓隊列在綁定Routing key 的時候使用通配符

Routingkey 一般都是有一個或多個單詞組成,多個單詞之間以”.”分割,例如: item.insert

通配符規(guī)則:
#:匹配一個或多個詞
*:匹配不多不少恰好1個詞

舉例:
item.#:能夠匹配item.insert.abc 或者 item.insert
item.*:只能匹配item.insert

image.png

image.png

圖解:

  • 紅色Queue:綁定的是usa.# ,因此凡是以 usa.開頭的routing key 都會被匹配到
  • 黃色Queue:綁定的是#.news ,因此凡是以 .news結(jié)尾的 routing key 都會被匹配

代碼

1)生產(chǎn)者

使用topic類型的Exchange,發(fā)送消息的routing key有3種: item.insertitem.updateitem.delete

package com.itheima.rabbitmq.topic;

import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

/**
 * 通配符Topic的交換機類型為:topic
 */
public class Producer {

    //交換機名稱
    static final String TOPIC_EXCHAGE = "topic_exchange";
    //隊列名稱
    static final String TOPIC_QUEUE_1 = "topic_queue_1";
    //隊列名稱
    static final String TOPIC_QUEUE_2 = "topic_queue_2";

    public static void main(String[] args) throws Exception {

        //創(chuàng)建連接
        Connection connection = ConnectionUtil.getConnection();

        // 創(chuàng)建頻道
        Channel channel = connection.createChannel();

        /**
         * 聲明交換機
         * 參數(shù)1:交換機名稱
         * 參數(shù)2:交換機類型,fanout、topic、topic、headers
         */
        channel.exchangeDeclare(TOPIC_EXCHAGE, BuiltinExchangeType.TOPIC);


        // 發(fā)送信息
        String message = "新增了商品。Topic模式;routing key 為 item.insert " ;
        channel.basicPublish(TOPIC_EXCHAGE, "item.insert", null, message.getBytes());
        System.out.println("已發(fā)送消息:" + message);

        // 發(fā)送信息
        message = "修改了商品。Topic模式;routing key 為 item.update" ;
        channel.basicPublish(TOPIC_EXCHAGE, "item.update", null, message.getBytes());
        System.out.println("已發(fā)送消息:" + message);

        // 發(fā)送信息
        message = "刪除了商品。Topic模式;routing key 為 item.delete" ;
        channel.basicPublish(TOPIC_EXCHAGE, "item.delete", null, message.getBytes());
        System.out.println("已發(fā)送消息:" + message);

        // 關(guān)閉資源
        channel.close();
        connection.close();
    }
}

消費者1

接收兩種類型的消息:更新商品和刪除商品

package com.itheima.rabbitmq.topic;

import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer1 {

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();

        // 創(chuàng)建頻道
        Channel channel = connection.createChannel();

        //聲明交換機
        channel.exchangeDeclare(Producer.TOPIC_EXCHAGE, BuiltinExchangeType.TOPIC);

        // 聲明(創(chuàng)建)隊列
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否定義持久化隊列
         * 參數(shù)3:是否獨占本次連接
         * 參數(shù)4:是否在不使用的時候自動刪除隊列
         * 參數(shù)5:隊列其它參數(shù)
         */
        channel.queueDeclare(Producer.TOPIC_QUEUE_1, true, false, false, null);

        //隊列綁定交換機
        channel.queueBind(Producer.TOPIC_QUEUE_1, Producer.TOPIC_EXCHAGE, "item.update");
        channel.queueBind(Producer.TOPIC_QUEUE_1, Producer.TOPIC_EXCHAGE, "item.delete");

        //創(chuàng)建消費者;并設(shè)置消息處理
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            /**
             * consumerTag 消息者標簽,在channel.basicConsume時候可以指定
             * envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發(fā)送)
             * properties 屬性信息
             * body 消息
             */
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key為:" + envelope.getRoutingKey());
                //交換機
                System.out.println("交換機為:" + envelope.getExchange());
                //消息id
                System.out.println("消息id為:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("消費者1-接收到的消息為:" + new String(body, "utf-8"));
            }
        };
        //監(jiān)聽消息
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否自動確認,設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動確認
         * 參數(shù)3:消息接收到后回調(diào)
         */
        channel.basicConsume(Producer.TOPIC_QUEUE_1, true, consumer);
    }
}

消費者2

接收所有類型的消息:新增商品,更新商品和刪除商品。

package com.itheima.rabbitmq.topic;

import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer2 {

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtil.getConnection();

        // 創(chuàng)建頻道
        Channel channel = connection.createChannel();

        //聲明交換機
        channel.exchangeDeclare(Producer.TOPIC_EXCHAGE, BuiltinExchangeType.TOPIC);

        // 聲明(創(chuàng)建)隊列
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否定義持久化隊列
         * 參數(shù)3:是否獨占本次連接
         * 參數(shù)4:是否在不使用的時候自動刪除隊列
         * 參數(shù)5:隊列其它參數(shù)
         */
        channel.queueDeclare(Producer.TOPIC_QUEUE_2, true, false, false, null);

        //隊列綁定交換機
        channel.queueBind(Producer.TOPIC_QUEUE_2, Producer.TOPIC_EXCHAGE, "item.*");

        //創(chuàng)建消費者;并設(shè)置消息處理
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            /**
             * consumerTag 消息者標簽,在channel.basicConsume時候可以指定
             * envelope 消息包的內(nèi)容,可從中獲取消息id,消息routingkey,交換機,消息和重傳標志(收到消息失敗后是否需要重新發(fā)送)
             * properties 屬性信息
             * body 消息
             */
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key為:" + envelope.getRoutingKey());
                //交換機
                System.out.println("交換機為:" + envelope.getExchange());
                //消息id
                System.out.println("消息id為:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("消費者2-接收到的消息為:" + new String(body, "utf-8"));
            }
        };
        //監(jiān)聽消息
        /**
         * 參數(shù)1:隊列名稱
         * 參數(shù)2:是否自動確認,設(shè)置為true為表示消息接收到自動向mq回復(fù)接收到了,mq接收到回復(fù)會刪除消息,設(shè)置為false則需要手動確認
         * 參數(shù)3:消息接收到后回調(diào)
         */
        channel.basicConsume(Producer.TOPIC_QUEUE_2, true, consumer);
    }
}

測試

啟動所有消費者,然后使用生產(chǎn)者發(fā)送消息;在消費者對應(yīng)的控制臺可以查看到生產(chǎn)者發(fā)送對應(yīng)routing key對應(yīng)隊列的消息;到達按照需要接收的效果;并且這些routing key可以使用通配符。

在執(zhí)行完測試代碼后,其實到RabbitMQ的管理后臺找到Exchanges選項卡,點擊 topic_exchange 的交換機,可以查看到如下的綁定:

image.png

小結(jié)

Topic主題模式可以實現(xiàn) Publish/Subscribe發(fā)布與訂閱模式Routing路由模式 的功能;只是Topic在配置routing key 的時候可以使用通配符,顯得更加靈活。

模式總結(jié)

RabbitMQ工作模式:
1、簡單模式 HelloWorld
一個生產(chǎn)者、一個消費者,不需要設(shè)置交換機(使用默認的交換機)

2、工作隊列模式 Work Queue
一個生產(chǎn)者、多個消費者(競爭關(guān)系),不需要設(shè)置交換機(使用默認的交換機)

3、發(fā)布訂閱模式 Publish/subscribe
需要設(shè)置類型為fanout的交換機,并且交換機和隊列進行綁定,當發(fā)送消息到交換機后,交換機會將消息發(fā)送到綁定的隊列

4、路由模式 Routing
需要設(shè)置類型為direct的交換機,交換機和隊列進行綁定,并且指定routing key,當發(fā)送消息到交換機后,交換機會根據(jù)routing key將消息發(fā)送到對應(yīng)的隊列

5、通配符模式 Topic
需要設(shè)置類型為topic的交換機,交換機和隊列進行綁定,并且指定通配符方式的routing key,當發(fā)送消息到交換機后,交換機會根據(jù)routing key將消息發(fā)送到對應(yīng)的隊列

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內(nèi)容