Spring整合rabbitmq實踐(一):基礎

Spring整合rabbitmq實踐(二):擴展
Spring整合rabbitmq實踐(三):源碼

1. Rabbitmq基本概念

image

1.1. Rabbitmq中的各個角色

producer:消息生產者;

consumer:消息消費者;

queue:消息隊列;

exchange:接收producer發送的消息按照binding規則轉發給相應的queue;

binding:exchange與queue之間的關系;

virtualHost:每個virtualHost持有自己的exchange、queue、binding,用戶只能在virtualHost粒度控制權限。

1.2. exchange的幾種類型

fanout:

群發到所有綁定的queue;

direct:

根據routing key routing到相應的queue,routing不到任何queue的消息扔掉;可以不同的key綁到同一個queue,也可以同一個key綁到不同的queue;

image
image

topic:

類似direct,區別是routing key是由一組以“.”分隔的單詞組成,可以有通配符,“*”匹配一個單詞,“#”匹配0個或多個單詞;

image

headers:

根據arguments來routing。

arguments為一組key-value對,任意設置。

“x-match”是一個特殊的key,值為“all”時必須匹配所有argument,值為“any”時只需匹配任意一個argument,不設置默認為“all”。

2. spring整合rabbitmq

通過以下配置,可以獲得最基礎的發送消息到queue,以及從queue接收消息的功能。

2.1. maven依賴


<dependency>

    <groupId>org.springframework.amqp</groupId>

    <artifactId>spring-rabbit</artifactId>

    <version>${version}</version>

</dependency>

這個包同時包含了一些其它的包:spring-context、spring-tx、spring-web、spring-messaging、spring-retry、spring-amqp、amqp-client,如果想單純一點,可以單獨引入。

最主要的是以下幾個包,

spring-amqp:

Spring AMQP Core.

spring-rabbit:

Spring RabbitMQ Support.

amqp-client:

The RabbitMQ Java client library allows Java applications to interface with RabbitMQ.

個人理解就是,spring-amqp是spring整合的amqp,spring-rabbit是spring整合的rabbitmq(rabbitmq是amqp的一個實現,所以可能spring-rabbit也是類似關系),amqp-client提供操作rabbitmq的java api。

目前最新的是2.0.5.RELEASE版本。如果編譯報錯,以下信息或許能有所幫助:

(1)


java.lang.ClassNotFoundException: org.springframework.amqp.support.converter.SmartMessageConverter

解決方案:spring-amqp版本改為2.0.5.RELEASE。

(2)


java.lang.IllegalStateException: LifecycleProcessor not initialized - call 'refresh' before invoking lifecycle methods via the context...

解決方案:spring-context版本改為5.0.7.RELEASE。

(3)


java.lang.NoSuchMethodError: org.springframework.util.ObjectUtils.unwrapOptional(Ljava/lang/Object;)Ljava/lang/Object

解決方案:spring-core版本改為5.0.7.RELEASE。

(4)


java.lang.NullPointerException: null

at org.springframework.core.BridgeMethodResolver.findBridgedMethod(BridgeMethodResolver.java:60)

at org.springframework.beans.GenericTypeAwarePropertyDescriptor.<init>(GenericTypeAwarePropertyDescriptor.java:70)

at org.springframework.beans.CachedIntrospectionResults.buildGenericTypeAwarePropertyDescriptor(CachedIntrospectionResults.java:366)

at org.springframework.beans.CachedIntrospectionResults.<init>(CachedIntrospectionResults.java:302)

解決方案:spring-beans版本改為5.0.7.RELEASE。

(5)


Caused by: java.lang.NoSuchMethodError: org.springframework.aop.framework.AopProxyUtils.getSingletonTarget(Ljava/lang/Object;)Ljava/lang/Object;

解決方案:spring-aop版本改為5.0.7.RELEASE。

總之,需要5.0.7.RELEASE版本的spring,及相匹配版本的amqp-client。

2.2. 配置ConnectionFactory

后面所講的這些bean配置,spring-amqp中都有默認配置,如果不需要修改默認配置,則不用人為配置這些bean。后面這些配置也沒有涉及到所有的屬性。

這里的ConnectionFactory指的是spring-rabbit包下面的ConnectionFactory接口,不是amqp-client包下面的ConnectionFactory類。


@Configuration

public class MqProducerConfig {

    @Autowired

    @Bean

    public ConnectionFactory amqpConnectionFactory(ConnectionListener connectionListener,

                                                  RecoveryListener recoveryListener,

                                                  ChannelListener channelListener) {

        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();

        connectionFactory.setAddresses("localhost:5672");

        connectionFactory.setUsername("guest");

        connectionFactory.setPassword("guest");

        connectionFactory.setVirtualHost("/");

        connectionFactory.setCacheMode(CachingConnectionFactory.CacheMode.CHANNEL);

        connectionFactory.setChannelCacheSize(25);

        connectionFactory.setChannelCheckoutTimeout(0);

        connectionFactory.setPublisherReturns(false);

        connectionFactory.setPublisherConfirms(false);

        connectionFactory.addConnectionListener(connectionListener);

        connectionFactory.addChannelListener(channelListener);

        connectionFactory.setRecoveryListener(recoveryListener);

        //connectionFactory.setConnectionCacheSize(1);

        //connectionFactory.setConnectionLimit(Integer.MAX_VALUE);

        return connectionFactory;

    }

}

上面這個bean是spring-amqp的核心,不論是發送消息還是接收消息都需要這個bean,下面描述一下里面這些配置的含義。

setAddresses:設置了rabbitmq的地址、端口,集群部署的情況下可填寫多個,“,”分隔。

setUsername:設置rabbitmq的用戶名。

setPassword:設置rabbitmq的用戶密碼。

setVirtualHost:設置virtualHost。

setCacheMode:設置緩存模式,共有兩種,CHANNELCONNECTION模式。

CHANNEL模式,程序運行期間ConnectionFactory會維護著一個Connection,所有的操作都會使用這個Connection,但一個Connection中可以有多個Channel,操作rabbitmq之前都必須先獲取到一個Channel,否則就會阻塞(可以通過setChannelCheckoutTimeout()設置等待時間),這些Channel會被緩存(緩存的數量可以通過setChannelCacheSize()設置);

CONNECTION模式,這個模式下允許創建多個Connection,會緩存一定數量的Connection,每個Connection中同樣會緩存一些Channel,除了可以有多個Connection,其它都跟CHANNEL模式一樣。

這里的Connection和Channel是spring-amqp中的概念,并非rabbitmq中的概念,官方文檔對Connection和Channel有這樣的描述:

Sharing of the connection is possible since the "unit of work" for messaging with AMQP is actually a "channel" (in some ways, this is similar to the relationship between a Connection and a Session in JMS).

關于CONNECTION模式中,可以存在多個Connection的使用場景,官方文檔的描述:

The use of separate connections might be useful in some environments, such as consuming from an HA cluster, in conjunction with a load balancer, to connect to different cluster members.

setChannelCacheSize:設置每個Connection中(注意是每個Connection)可以緩存的Channel數量,注意只是緩存的Channel數量,不是Channel的數量上限,操作rabbitmq之前(send/receive message等)要先獲取到一個Channel,獲取Channel時會先從緩存中找閑置的Channel,如果沒有則創建新的Channel,當Channel數量大于緩存數量時,多出來沒法放進緩存的會被關閉。

注意,改變這個值不會影響已經存在的Connection,只影響之后創建的Connection。

setChannelCheckoutTimeout:當這個值大于0時,channelCacheSize不僅是緩存數量,同時也會變成數量上限,從緩存獲取不到可用的Channel時,不會創建新的Channel,會等待這個值設置的毫秒數,到時間仍然獲取不到可用的Channel會拋出AmqpTimeoutException異常。

同時,在CONNECTION模式,這個值也會影響獲取Connection的等待時間,超時獲取不到Connection也會拋出AmqpTimeoutException異常。

setPublisherReturns、setPublisherConfirms:producer端的消息確認機制(confirm和return),設為true后開啟相應的機制,后文詳述。

官方文檔描述publisherReturns設為true打開return機制,publisherComfirms設為true打開confirm機制,但測試結果(2.0.5.RELEASE版本)是,任意一個設為true,兩個都會打開。

addConnectionListener、addChannelListener、setRecoveryListener:添加或設置相應的Listener,后文詳述。

setConnectionCacheSize:僅在CONNECTION模式使用,設置Connection的緩存數量。

setConnectionLimit:僅在CONNECTION模式使用,設置Connection的數量上限。

上面的bean配置,除了需要注入的幾個listener bean以外,其它設置的都是其默認值(2.0.5.RELEASE版本),后面的bean示例配置也是一樣,部分屬性不同版本的默認值可能有所不同。

2.3. 配置com.rabbitmq.client.ConnectionFactory

一般不用配置這個bean,這里簡單提一下。

這個ConnectionFactory是rabbit api中的ConnectionFactory類,這里面是連接rabbitmq節點的Connection配置。


    /** Default user name */

    public static final String DEFAULT_USER = "guest";

    /** Default password */

    public static final String DEFAULT_PASS = "guest";

    /** Default virtual host */

    public static final String DEFAULT_VHOST = "/";

    /** Default maximum channel number;

    *  zero for unlimited */

    public static final int    DEFAULT_CHANNEL_MAX = 0;

    /** Default maximum frame size;

    *  zero means no limit */

    public static final int    DEFAULT_FRAME_MAX = 0;

    /** Default heart-beat interval;

    *  60 seconds */

    public static final int    DEFAULT_HEARTBEAT = 60;

    /** The default host */

    public static final String DEFAULT_HOST = "localhost";

    /** 'Use the default port' port */

    public static final int    USE_DEFAULT_PORT = -1;

    /** The default non-ssl port */

    public static final int    DEFAULT_AMQP_PORT = AMQP.PROTOCOL.PORT;

    /** The default ssl port */

    public static final int    DEFAULT_AMQP_OVER_SSL_PORT = 5671;

    /** The default TCP connection timeout: 60 seconds */

    public static final int    DEFAULT_CONNECTION_TIMEOUT = 60000;

    /**

    * The default AMQP 0-9-1 connection handshake timeout. See DEFAULT_CONNECTION_TIMEOUT

    * for TCP (socket) connection timeout.

    */

    public static final int    DEFAULT_HANDSHAKE_TIMEOUT = 10000;

    /** The default shutdown timeout;

    *  zero means wait indefinitely */

    public static final int    DEFAULT_SHUTDOWN_TIMEOUT = 10000;

    /** The default continuation timeout for RPC calls in channels: 10 minutes */

    public static final int    DEFAULT_CHANNEL_RPC_TIMEOUT = (int) MINUTES.toMillis(10);

    /** The default network recovery interval: 5000 millis */

    public static final long  DEFAULT_NETWORK_RECOVERY_INTERVAL = 5000;

    private static final String PREFERRED_TLS_PROTOCOL = "TLSv1.2";

    private static final String FALLBACK_TLS_PROTOCOL = "TLSv1";

    ...

如果想修改這些配置,可以按如下方式配置:


    @Autowired

    @Bean

    public ConnectionFactory connectionFactory(com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory) {

        CachingConnectionFactory connectionFactory = new CachingConnectionFactory(rabbitConnectionFactory);

        return connectionFactory;

    }

    @Bean

    public com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory() {

        com.rabbitmq.client.ConnectionFactory connectionFactory = new com.rabbitmq.client.ConnectionFactory();

        connectionFactory.setAutomaticRecoveryEnabled(false);

        connectionFactory.setUsername("guest");

        connectionFactory.setPassword("guest");

        connectionFactory.setVirtualHost("/");

        return connectionFactory;

    }

2.4. 配置AmqpTemplate

consumer端如果通過@RabbitListener注解的方式接收消息,不需要這個bean。

不建議直接通過ConnectionFactory獲取Channel操作rabbitmq,建議通過amqpTemplate操作。


    @Autowired

    @Bean

    public AmqpTemplate amqpTemplate(ConnectionFactory amqpConnectionFactory,

                                    RabbitTemplate.ReturnCallback returnCallback,

                                    RabbitTemplate.ConfirmCallback confirmCallback,

                                    RetryTemplate retryTemplate,

                                    MessageConverter messageConverter

                                    ){

        RabbitTemplate rabbitTemplate = new RabbitTemplate();

        rabbitTemplate.setConnectionFactory(amqpConnectionFactory);

        rabbitTemplate.setRetryTemplate(retryTemplate);

        rabbitTemplate.setMessageConverter(messageConverter);

        rabbitTemplate.setChannelTransacted(false);

        rabbitTemplate.setReturnCallback(returnCallback);

        rabbitTemplate.setConfirmCallback(confirmCallback);

        rabbitTemplate.setMandatory(false);

        return rabbitTemplate;

    }

setConnectionFactory:設置spring-amqp的ConnectionFactory。

setRetryTemplate:設置重試機制,詳情見后文。

setMessageConverter:設置MessageConverter,用于java對象與Message對象(實際發送和接收的消息對象)之間的相互轉換,詳情見后文。

setChannelTransacted:打開或關閉Channel的事務,關于amqp的事務后文描述。

setReturnCallback、setConfirmCallback:return和confirm機制的回調接口,后文詳述。

setMandatory:設為true使ReturnCallback生效。

2.5. 配置RabbitListenerContainerFactory

這個bean僅在consumer端通過@RabbitListener注解的方式接收消息時使用,每一個@RabbitListener注解的方法都會由這個RabbitListenerContainerFactory創建一個MessageListenerContainer,負責接收消息。


    @Autowired

    @Bean

    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(CachingConnectionFactory cachingConnectionFactory,

                                                                              ErrorHandler errorHandler,

                                                                              MessageConverter messageConverter) {

        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();

        factory.setConnectionFactory(cachingConnectionFactory);

        factory.setMessageConverter(messageConverter);

        factory.setAcknowledgeMode(AcknowledgeMode.AUTO);

        factory.setConcurrentConsumers(1);

        factory.setMaxConcurrentConsumers(1);

        factory.setPrefetchCount(250);

        factory.setChannelTransacted(false);

        factory.setTxSize(1);

        factory.setDefaultRequeueRejected(true);

        factory.setErrorHandler(errorHandler);

        return factory;

    }

setConnectionFactory:設置spring-amqp的ConnectionFactory。

setMessageConverter:對于consumer端,MessageConverter也可以在這里配置。

setAcknowledgeMode:設置consumer端的應答模式,共有三種:NONE、AUTO、MANUAL。

NONE,無應答,這種模式下rabbitmq默認consumer能正確處理所有發出的消息,所以不管消息有沒有被consumer收到,有沒有正確處理都不會恢復;

AUTO,由Container自動應答,正確處理發出ack信息,處理失敗發出nack信息,rabbitmq發出消息后將會等待consumer端的應答,只有收到ack確認信息才會把消息清除掉,收到nack信息的處理辦法由setDefaultRequeueRejected()方法設置,所以在這種模式下,發生錯誤的消息是可以恢復的。

MANUAL,基本同AUTO模式,區別是需要人為調用方法給應答。

setConcurrentConsumers:設置每個MessageListenerContainer將會創建的Consumer的最小數量,默認是1個。

setMaxConcurrentConsumers:設置每個MessageListenerContainer將會創建的Consumer的最大數量,默認等于最小數量。

setPrefetchCount:設置每次請求發送給每個Consumer的消息數量。

setChannelTransacted:設置Channel的事務。

setTxSize:設置事務當中可以處理的消息數量。

setDefaultRequeueRejected:設置當rabbitmq收到nack/reject確認信息時的處理方式,設為true,扔回queue頭部,設為false,丟棄。

setErrorHandler:實現ErrorHandler接口設置進去,所有未catch的異常都會由ErrorHandler處理。

2.6. sending messages

AmqpTamplate里面有下面幾個方法可以向queue發送消息:


void send(Message message) throws AmqpException;

void send(String routingKey, Message message) throws AmqpException;

void send(String exchange, String routingKey, Message message) throws AmqpException;

這里,exchange必須存在,否則消息發不出去,會看到錯誤日志,但不影響程序運行:


03:57:38.700 [AMQP Connection 127.0.0.1:5672] ERROR [CachingConnectionFactory.java:956] - Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'xxx' in vhost 'test', class-id=60, method-id=40)

Message是org.springframework.amqp.core.Message類,spring-amqp發送和接收的都是這個Message。


package org.springframework.amqp.core;

public class Message implements Serializable {

private static final long serialVersionUID = -7177590352110605597L;

private static final String ENCODING = Charset.defaultCharset().name();

private final MessageProperties messageProperties;

private final byte[] body;

public Message(byte[] body, MessageProperties messageProperties) { //NOSONAR

this.body = body; //NOSONAR

this.messageProperties = messageProperties;

}

...

}

從Message類源碼可以看到消息內容放在byte[]里面,MessageProperties對象包含了非常多的一些其它信息,如Header、exchange、routing key等。

這種方式,需要將消息內容(String,或其它Object)轉換為byte[],示例:


amqpTemplate.send(exchange, routingKey, new Message(JSON.toJSONString(event).getBytes(), MessagePropertiesBuilder.newInstance().build()));

也可以直接調用下面幾個方法,Object將會自動轉為Message對象發送:


void convertAndSend(Object message) throws AmqpException;

void convertAndSend(String routingKey, Object message) throws AmqpException;

void convertAndSend(String exchange, String routingKey, Object message)

    throws AmqpException;

2.7. receiving messages

有兩種方法接收消息:

1.polling consumer,輪詢調用方法一次獲取一條;

2.asynchronous consumer,listener異步接收消息。

polling consumer

直接通過AmqpTemplate的方法從queue獲取消息,有如下方法:


Message receive() throws AmqpException;

Message receive(String queueName) throws AmqpException;

Message receive(long timeoutMillis) throws AmqpException;

Message receive(String queueName, long timeoutMillis) throws AmqpException;

如果queue里面沒有消息,會立刻返回null;傳入timeoutMillis參數后可阻塞等待一段時間。

如果想直接從queue獲取想要的java對象,可調用下面這一組方法:


    Object receiveAndConvert() throws AmqpException;

Object receiveAndConvert(String queueName) throws AmqpException;

Object receiveAndConvert(long timeoutMillis) throws AmqpException;

Object receiveAndConvert(String queueName, long timeoutMillis) throws AmqpException;

<T> T receiveAndConvert(ParameterizedTypeReference<T> type) throws AmqpException;

<T> T receiveAndConvert(String queueName, ParameterizedTypeReference<T> type) throws AmqpException;

<T> T receiveAndConvert(long timeoutMillis, ParameterizedTypeReference<T> type) throws AmqpException;

<T> T receiveAndConvert(String queueName, long timeoutMillis, ParameterizedTypeReference<T> type)

throws AmqpException;

后面4個方法是帶泛型的,示例如下:


Foo<Bar<Baz, Qux>> foo =

    rabbitTemplate.receiveAndConvert(new ParameterizedTypeReference<Foo<Bar<Baz, Qux>>>() { });

使用這四個方法需要配置org.springframework.amqp.support.converter.SmartMessageConverter,這是一個接口,Jackson2JsonMessageConverter已經實現了這個接口,所以只要將Jackson2JsonMessageConverter設置到RabbitTemplate中即可。

asynchronous consumer

有多種方式可以實現,詳情參考官方文檔。

最簡單的實現方式是@RabbitListener注解,示例:


@Component

public class RabbitMqListener {

    @RabbitListener(queues = "queueName")

    public void listen(Message message) {

        JSON.parseObject(new String(message.getBody()), typeReference);

    }

}

這里接收消息的對象用的是Message,也可以是自定義的java對象,但調用Converter轉換失敗會報錯。

注解上指定的queue必須是已經存在并且綁定到某個exchange的,否則會報錯:


03:46:59.746 [SimpleAsyncTaskExecutor-1] WARN  o.s.a.r.l.BlockingQueueConsumer [BlockingQueueConsumer.java:565] - Failed to declare queue:xxx

03:46:59.747 [SimpleAsyncTaskExecutor-1] WARN  o.s.a.r.l.BlockingQueueConsumer [BlockingQueueConsumer.java:479] - Queue declaration failed; retries left=3

org.springframework.amqp.rabbit.listener.BlockingQueueConsumer$DeclarationException: Failed to declare queue(s):[xxx]

如果在@RabbitListener注解中指明binding信息,就能自動創建queue、exchange并建立binding關系。

direct和topic類型的exchange需要routingKey,示例:


@RabbitListener(bindings = @QueueBinding(

        value = @Queue(value = "myQueue", durable = "true"),

        exchange = @Exchange(value = "auto.exch", durable = "true"),

        key = "orderRoutingKey.#")

  )

fanout類型的exchange,示例:


@RabbitListener(bindings = @QueueBinding(

        value = @Queue(value = "myQueue", durable = "true"),

        exchange = @Exchange(value = "auto.exch", durable = "true", type = ExchangeTypes.FANOUT)

        )

  )

2.0版本之后,可以指定多個routingKey,示例:


key = { "red", "yellow" }

并且支持arguments屬性,可用于headers類型的exchange,示例:


@RabbitListener(bindings = @QueueBinding(

        value = @Queue(value = "auto.headers", autoDelete = "true",

                        arguments = @Argument(name = "x-message-ttl", value = "10000",

                                                type = "java.lang.Integer")),

        exchange = @Exchange(value = "auto.headers", type = ExchangeTypes.HEADERS, autoDelete = "true"),

        arguments = {

                @Argument(name = "x-match", value = "all"),

                @Argument(name = "foo", value = "bar"),

                @Argument(name = "baz")

        })

)

@Queue有兩個參數exclusive和autoDelete順便解釋一下:

exclusive,排他隊列,只對創建這個queue的Connection可見,Connection關閉queue刪除;

autoDelete,沒有consumer對這個queue消費時刪除。

對于這兩種隊列,durable=true是不起作用的。

另外,如果注解申明的queue和exchange及binding關系都已經存在,但與已存在的設置不同,比如,已存在的exchange的是direct類型,這里嘗試改為fanout類型,結果是不會有任何影響,不論是修改或者新增參數都不會生效。

如果queue存在,exchange存在,但沒有binding,那么程序啟動后會自動建立起binding關系。

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

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,804評論 18 139
  • http://liuxing.info/2017/06/30/Spring%20AMQP%E4%B8%AD%E6%...
    sherlock_6981閱讀 15,972評論 2 11
  • Spring整合rabbitmq實踐(一):基礎Spring整合rabbitmq實踐(三):源碼 3. 擴展實踐 ...
    jinchaolv閱讀 10,118評論 1 7
  • 來源 RabbitMQ是用Erlang實現的一個高并發高可靠AMQP消息隊列服務器。支持消息的持久化、事務、擁塞控...
    jiangmo閱讀 10,377評論 2 34
  • 前言 在微服務架構的系統中,我們通常會使用輕量級的消息代理來構建一個共用的消息主題讓系統中所有微服務實例都連接上來...
    Chandler_玨瑜閱讀 6,600評論 2 39