Spring 整合RabbitMQ實例

了解RabbitMQ

RabbitMQ使用場景

Spring 整合RabbitMQ

配置maven : pom.xml
<dependency>
      <groupId>com.rabbitmq</groupId>
      <artifactId>amqp-client</artifactId>
      <version>3.5.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-rabbit</artifactId>
        <version>1.4.5.RELEASE</version>
    </dependency>
1.配置連接信息 : rabbit.properties 文件
//連接信息
rabbit.vhost=/test  //虛擬機
rabbit.addresses=10.166.27.51:5672//服務器 端口
rabbit.username=test//用戶名
rabbit.password=test//密碼
channel.cache.size=50
//exchange 交換機配置名稱
exchange.direct=test//交換機名
//配置隊列名
queue.sync_nc=newretail.queue.sync_nc
queue.sync_nc_error=newretail.queue.sync_nc_error
queue.reply=queue.reply
//隊列交換機的路由鍵
route.sync_nc=route.queue.sync_nc
route.reply=route.reply
route.sync_nc_error=route.queue.sync_nc_error
2. 依賴注入 : spring-rabbit.xml
rabbit 命名空間包含了多個元素,幫助我們聲明隊列、Exchange 以及將它們結合在一起的 binding
元素 作用
<queue> 創建一個隊列
<fanout-exchange> 創建一個 fanout 類型的 Exchange
<header-exchange> 創建一個 header 類型的 Exchange
<topic-exchange> 創建一個 topic 類型的 Exchange
<direct-exchange> 創建一個 direct 類型的 Exchange
<bindings><binding></bindings> 元素定義一個或多個元素的集合。元素創建 Exchange 和隊列之間的 binding
這些配置元素要與 <admin> 元素一起使用。
<admin> 元素會創建一個 RabbitMQ 管理組件(administrative component),
它會自動創建 (如果它們在 RabbitMQ 代理中尚未存在的話)上述這些元素所聲明的隊列、Exchange 以及 binding。
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:rabbit="http://www.springframework.org/schema/rabbit"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/rabbit
        http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">

      <!--引用ExecutorService或ThreadPoolTask??Executor(由<task:executor />元素定義)。創建連接時傳遞給Rabbit庫。如果未提供,Rabbit庫當前使用具有5個線程的固定線程池ExecutorService-->
        <task:executor id="poolTaskExecutor"
                   pool-size="10"
                   queue-capacity="10"
                   keep-alive="20"
                   rejection-policy="DISCARD_OLDEST"/>

    <!-- RabbitMQ 連接工廠公共配置 連接服務-->
    <rabbit:connection-factory id="rabbitConnectionFactory"
        addresses="${rabbit.addresses}" 
        virtual-host="${rabbit.vhost}"
        username="${rabbit.username}" 
        password="${rabbit.password}"
<!--上面配置的線程池 一般不需配置默認 即可-->
                executor="poolTaskExecutor"
<!-- channel-cache-size,channel的緩存數量,默認值為25-->
        channel-cache-size="${channel.cache.size}" 
<!-- 設置此屬性配置可以確保消息成功發送到交換器-->
        publisher-confirms="true"
<!-- 可以確保消息在未被隊列接收時返回-->
        publisher-returns="true"    
                <!-- cache-mode 一般不需要配置,緩存連接模式,
默認值為CHANNEL(單個connection連接,連接之后關閉,自動銷毀) -->
                cache-mode="CHANNEL" />
<!-- <admin> 元素會創建一個 RabbitMQ 管理組件(administrative component)-->
    <rabbit:admin connection-factory="rabbitConnectionFactory" />
    <!-- 隊列配置 -->
<!--定義消息隊列,durable:是否持久化,
如果想在RabbitMQ退出或崩潰的時候,不會失去所有的queue和消息,
需要同時標志隊列(queue)和交換機(exchange)是持久化的,即rabbit:queue標簽和rabbit:direct-exchange中的durable=true,
而消息(message)默認是持久化的可以看類org.springframework.amqp.core.MessageProperties中的屬性
public static final MessageDeliveryMode DEFAULT_DELIVERY_MODE = MessageDeliveryMode.PERSISTENT;
exclusive: 僅創建者可以使用的私有隊列,斷開后自動刪除;
auto_delete: 當所有消費客戶端連接斷開后,是否自動刪除隊列 -->
    <rabbit:queue name="${queue.sync_nc}" durable="true" auto-delete="false" exclusive="false">
        <rabbit:queue-arguments>
            <entry key="x-max-priority">
                <value type="java.lang.Integer">10</value> 
<!-- 設定隊列支持的最大優先級:rabbit3.5以上支持,3.5以下 需要安裝插件 -->
            </entry>  
        </rabbit:queue-arguments>
    </rabbit:queue>
<rabbit:queue name="${queue.reply}" durable="true" auto-delete="false" exclusive="false" />
    <!-- 交換機配置 -->
<!--綁定隊列,rabbitmq的exchangeType常用的三種模式:direct,fanout,topic三種,
我們用direct模式,即rabbit:direct-exchange標簽,
Direct交換器很簡單,如果是Direct類型,就會將消息中的RoutingKey與該Exchange關聯的所有Binding中的BindingKey進行比較,如果相等,則發送到該Binding對應的Queue中。有一個需要注意的地方:如果找不到指定的exchange,就會報錯。但routing key找不到的話,不會報錯,這條消息會直接丟失,所以此處要小心,
auto-delete:自動刪除,如果為Yes,則該交換機所有隊列queue刪除后,自動刪除交換機,默認為false -->
    <rabbit:direct-exchange name="${exchange.direct}" durable="true" auto-delete="false">
        <rabbit:bindings>
            <rabbit:binding queue="${queue.sync_nc}" key="${route.sync_nc}" />
                <rabbit:binding queue="${queue.reply}" key="${route.reply}" />
        </rabbit:bindings>
    </rabbit:direct-exchange>
    <!-- 生產者配置 -->
      <!--實例化兩個ben處理消息到達交換機,和消息進入隊列的節點-->
    <bean id="messageConfirm" class="com.sjky.platform.common.rabbit.MessageConfirm" />
    <bean id="messageReturn" class="com.sjky.platform.common.rabbit.MessageReturn" />
        <!--spring 為amqp默認的是jackson的一個插件生產者生產的數據轉換為json存入消息隊列-->
    <bean id="messageConverter"
           class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter">
    </bean>
    <rabbit:template id="rabbitTemplate"
        connection-factory="rabbitConnectionFactory" //連接工廠
        message-converter="messageConverter"
        exchange="${exchange.direct}" 
        reply-timeout="2000"   //發送和接收操作的超時時間(以毫秒為單位)。默認值為5000(5秒)
        retry-template="retryTemplate" 
        mandatory="true"
<!--引用上面實體,消息到達交換機時被調用-->
        confirm-callback="messageConfirm"
<!--應用上面定義的實體,消息無法到達隊列是使用-->
        return-callback="messageReturn"
    />
    <!-- retryTemplate為連接失敗時的重試模板 -->
    <bean id="retryTemplate" class="org.springframework.retry.support.RetryTemplate">
        <property name="backOffPolicy">
            <bean class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
                <property name="initialInterval" value="5000" />
                <property name="multiplier" value="10.0" />
                <property name="maxInterval" value="10000" />
            </bean>
        </property>
        <property name="retryPolicy">
            <bean class="org.springframework.retry.policy.SimpleRetryPolicy">
                <property name="maxAttempts" value="3"/>
            </bean>
        </property>
    </bean>
    <!--在任何Spring管理的對象上啟用對@RabbitListener批注的檢測-->
    <rabbit:annotation-driven />
<!-- 消費者配置AUTO方式 -->
    <bean id="rabbitListenerContainerFactory"
        class="org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory">
        <property name="messageConverter" ref="messageConverter" />
        <property name="connectionFactory" ref="rabbitConnectionFactory" />
        <property name="concurrentConsumers" value="9" />
        <property name="maxConcurrentConsumers" value="50" />
        <property name="taskExecutor" ref="taskExecutor" />
        <property name="prefetchCount" value="3" /> <!-- 每次1個 -->
    <!--選項: NONE,MANUAL,AUTO 默認:AUTO,當為MANUAL時必須調用Channel.basicAck()來手動應答所有消息 -->
        <property name="acknowledgeMode" value="AUTO" />
        <property name="errorHandler" ref="mqErrorHandler" /><!-- 引用注冊的實體,處理未捕獲異常 -->
    </bean>
      <!--處理未捕獲異常-->
    <bean id="mqErrorHandler" class="com.sjky.platform.common.rabbit.MQErrorHandler" />
    <!-- 消費者配置MANUAL方式 -->
    <!-- <bean id="rabbitListenerContainerFactory"
        class="org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory">
        <property name="messageConverter" ref="messageConverter" />
        <property name="connectionFactory" ref="rabbitConnectionFactory" />
        <property name="concurrentConsumers" value="1" />
        <property name="maxConcurrentConsumers" value="100" />
        <property name="taskExecutor" ref="taskExecutor" />
        <property name="prefetchCount" value="1" /> 
        選項: NONE,MANUAL,AUTO 默認:AUTO,當為MANUAL時必須調用Channel.basicAck()來手動應答所有消息
        <property name="acknowledgeMode" value="MANUAL" />
        <property name="errorHandler" ref="mqErrorHandler" /> 處理未捕獲異常
        <property name="adviceChain" ref="retryOperationsInterceptorFactoryBean" />
    </bean> 
    <bean id="mqMessageRecover" class="com.sjky.platform.common.rabbit.MQMsgRecover"/>
    -->
    <!-- 實現異常事件處理邏輯 -->
    <!-- <bean id="retryOperationsInterceptorFactoryBean"
        class="org.springframework.amqp.rabbit.config.StatelessRetryOperationsInterceptorFactoryBean">
        <property name="messageRecoverer" ref="mqMessageRecover" />配置消息恢復者
        <property name="retryOperations" ref="retryTemplate" />配置重試模板
    </bean> -->
</beans>
3. 注入時需要的Rabbit工具類
/**
 * 消息到達交換機時被調用
 * ack=true 成功 ,ack=false 失敗
*/

public class MessageConfirm implements ConfirmCallback {

    private static final Logger logger = LoggerFactory.getLogger(MessageConfirm.class);
    
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {

        if (!ack) {
            logger.error("消息無法到達交換機[ack: " + ack + ",correlationData: " + correlationData + ",cause : " + cause+"].");
        }else {
            logger.info("消息到達交換機[ack: " + ack + ",correlationData: " + correlationData + ",cause : " + cause+"].");
        }
    }

}

/**
 * Exchange無法將消息路由到任何隊列時會被調用
*/
public class MessageReturn implements ReturnCallback {
    
    private static final Logger logger = LoggerFactory.getLogger(MessageReturn.class);
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        //將消息發送到錯誤隊列
        RepublishMessageRecoverer recoverer = new RepublishMessageRecoverer(rabbitTemplate,RabbitConfig.getDirectExchange(), RabbitConfig.getErrorRoute());
        Exception cause = new Exception(new Exception("route fail and republish"));
        recoverer.recover(message,cause);
        logger.error("消息無法到達隊列[Returned Message: " + replyText + ",code: " + replyCode + ",exchange: " + exchange + ",routingKey :" + routingKey+"].");
    }

}
/**
 * 消費時  消費失敗 拋出異常時 調用
*/
public class MQErrorHandler implements ErrorHandler {
    
    private static final Logger logger = LoggerFactory.getLogger(MQErrorHandler.class);

    @Override
    public void handleError(Throwable cause) {
        logger.error("一個錯誤發生了:", cause);
    }
    
}
/**
 * acknowledgeMode=MANUL時的隊列監聽出現異常時消息恢復到隊列
 * 注:必須調用channel.basicAck()確認回執
 *  <!-- 消費者配置示例 -->
    <bean id="rabbitListenerContainerFactory"
        class="org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory">
        <property name="messageConverter" ref="messageConverter" />
        <property name="connectionFactory" ref="rabbitConnectionFactory" />
        <property name="concurrentConsumers" value="1" />
        <property name="maxConcurrentConsumers" value="100" />
        <property name="taskExecutor" ref="taskExecutor" />
        <property name="prefetchCount" value="1" /> 
        <!--選項: NONE,MANUAL,AUTO 默認:AUTO,當為MANUAL時必須調用Channel.basicAck()來手動應答所有消息 -->
        <property name="acknowledgeMode" value="MANUAL" />
        <property name="errorHandler" ref="mqErrorHandler" /> <!-- 處理未捕獲異常 -->
        <property name="adviceChain" ref="retryOperationsInterceptorFactoryBean" />
    </bean>
    <bean id="mqErrorHandler" class="com.sjky.platform.common.rabbit.MQErrorHandler" />
    <bean id="mqMessageRecover" class="com.sjky.platform.common.rabbit.MQMsgRecover"/>
    <!-- 實現異常事件處理邏輯 -->
    <bean id="retryOperationsInterceptorFactoryBean"
        class="org.springframework.amqp.rabbit.config.StatelessRetryOperationsInterceptorFactoryBean">
        <property name="messageRecoverer" ref="mqMessageRecover" />
        <property name="retryOperations" ref="retryTemplate" />
    </bean>
 *
 */
public class MQMessageRecover implements MessageRecoverer {

    private static final Logger logger = LoggerFactory.getLogger(MQMessageRecover.class);
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private Jackson2JsonMessageConverter msgConverter;
    
    @Override
    public void recover(Message message, Throwable cause) {
        String data=msgConverter.fromMessage(message).toString();
        MessageProperties messageProperties=message.getMessageProperties();
        Map<String, Object> headers = messageProperties.getHeaders();
        headers.put("x-exception-stacktrace", getStackTraceAsString(cause));
        headers.put("x-exception-message", cause.getCause() != null ? cause.getCause().getMessage() : cause.getMessage());
        headers.put("x-original-exchange", message.getMessageProperties().getReceivedExchange());
        headers.put("x-original-routingKey", message.getMessageProperties().getReceivedRoutingKey());
        messageProperties.setReceivedDeliveryMode(MessageDeliveryMode.PERSISTENT);
        //重新將數據放回隊列中
        rabbitTemplate.send(messageProperties.getReceivedExchange(), messageProperties.getReceivedRoutingKey(), message);
        logger.error("處理消息(" + data + ") 錯誤, 重新發布去隊列.", cause);
    }
    
    private String getStackTraceAsString(Throwable cause) {
        StringWriter stringWriter = new StringWriter();
        PrintWriter printWriter = new PrintWriter(stringWriter, true);
        cause.printStackTrace(printWriter);
        return stringWriter.getBuffer().toString();
    }
4. 將消息添加到隊列
/**
 * @description: 添加請求到隊列
 * @author: xhh
 * @create: 2020-10-22 14:24
 **/
@Controller
@RequestMapping("/nc/***/")
public class RabbirController extends BaseController {

    @PostMapping("addOrderToQueue")
    @ResponseBody
    public ResultData addOrderToQueue(@RequestParam String orderCode, HttpServletRequest request){
        ResultData resultData = new ResultData();
        resultData.setResult(true);
        resultData.setMessage("加入隊列成功!");
        logger.info("=================系統請求將訂單code:{}放入隊列=====================",orderCode);
        try {
            //判斷訂單是否加入過隊列
            String userCode = WebUtils.getLoginUserCode(request);
            HashMap<String, String> stringStringHashMap = new HashMap<>(2);
            stringStringHashMap.put("code",orderCode);
            stringStringHashMap.put("createUserCode",userCode);
            //將訂單號發送到指定交換機的隊列 (交換機,陸游鍵,消息內容)
rabbitTemplate.convertAndSend(RabbitConfig.getDirectExchange(),RabbitConfig.getSyncNcRoute(),stringStringHashMap);
          } catch (Exception e) {
            resultData.setResult(false);
            resultData.setMessage(e.getMessage());
            e.printStackTrace();
        }
        return resultData;
    }
}
5. 監聽 消費隊列
/**
 * @description: 監聽同步nc的隊列
 * @author: xhh
 * @create: 2020-10-23 16:36
 **/
@Service
public class ToNcQueueListener {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());


    /*****
     * @Description: 監聽同步NC的隊列
     * @Param: [message]
     * @return: void
     * @Author: xh
     * @Date: 2020/10/27 16:32
     */
    @RabbitListener(queues = "${queue.sync_nc}")//此注解 監聽的隊列名稱
    public void processMsg(Message message) throws Exception {
        String data = new String(message.getBody());
        JSONObject jsonObject = JSONObject.parseObject(data);
        //訂單號
        String orderCode = jsonObject.getString("code");
        //登錄員工code
        String userCode = jsonObject.getString("createUserCode");
        logger.info("======================監聽到訂單信息:{}=============================", data);
    }
}

浪客行1213的簡書


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

推薦閱讀更多精彩內容