了解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