SpringMVC + RabbitMQ实战(XML配置rabbit,监听多条队列)

本文介绍了解决springboot项目与sapjco3.jar兼容性问题的方法,通过使用SpringMVC+本地Tomcat发布模式,避免了jar包重命名问题。详细讲述了两个项目案例:api-rabbit-mvc与rfc-rabbit-mvc,前者生产requestQueues并消费responseQueues,后者相反。同时,分享了在Linux环境下部署war包及配置sapjco环境变量的经验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、为什么使用SpringMVC整合RabbitMQ?

由于springboot项目rabbit-rfc与sapjco3.jar的不兼容性,具体表现为:在Linux上执行java -jar rabbit-rfc.jar时,出现以下错误:

It is not allowed to rename or repackage the original archive “sapjco3.jar”

这是因为springboot项目依赖jar包的默认打包规则即为Maven的默认打包规则:

%artifactId%-%version%.jar

所以当sapjco3在maven中的dependency如下时:

 <dependency>
     <groupId>com.sap</groupId>
     <artifactId>sapjco3</artifactId>
     <version>3.0.12</version>
     <scope>system</scope>
     <systemPath>${project.basedir}/src/main/webapp/WEB-INF/lib/sapjco3.jar</systemPath>
 </dependency>

sapjco3.jar最终在package项目jar包时会被重新打包命名为sapjco3-3.0.12.jar,所以才会出现上述不允许重命名或重打包sapjco3.jar的错误。
具体参考stackoverflow的这个问题:
https://stackoverflow.com/questions/36924961/sapjco3-jar-has-issues-with-mavens-naming-convention-springboot
解决此问题的方法之一就是不要让sapjco3.jar在打包或运行时被重命名,所以使用SpringMVC + 本地Tomcat的办法,在IntelliJ Idea 中配置为发布模式war,便可以在项目启动时在target目录中生成以项目名命名的war包。
在这里插入图片描述
注:后来此问题已被解决,见我的博客:《sapjco问题记录及解决办法》https://blog.youkuaiyun.com/qq_15329947/article/details/87893288
附:IntelliJ Idea war和war exploded的区别

是选择war还是war exploded 这里首先看一下他们两个的区别:
war模式:将WEB工程以包的形式上传到服务器 ; war exploded模式:将WEB工程以当前文件夹的位置关系上传到服务器;
(1)war模式这种可以称之为是发布模式,看名字也知道,这是先打成war包,再发布;
(2)war exploded模式是直接把文件夹、jsp页面 、classes等等移到Tomcat
部署文件夹里面,进行加载部署。因此这种方式支持热部署,一般在开发的时候也是用这种方式。
(3)在平时开发的时候,使用热部署的话,应该对Tomcat进行相应的设置,这样的话修改的jsp界面什么的东西才可以及时的显示出来。

二、项目实战

项目架构参考我的博文《RabbitMQ中间件方案:BPM-SAP的应用解耦和用户并发量控制》、SpringMVC的其他配置参考《SpringMVC + Spring + Hibernate实战(通用配置)》。
以下两个项目需要引入必要的依赖:

    <dependency>
      <groupId>org.springframework.amqp</groupId>
      <artifactId>spring-rabbit</artifactId>
      <version>2.1.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>com.rabbitmq</groupId>
      <artifactId>amqp-client</artifactId>
      <version>5.4.3</version>
    </dependency>

版本号没必要拘泥,进入maven repository官网,引入最新版本的依赖即可。

项目一:api-rabbit-mvc,生产requestQueues,消费responseQueues

创建5条request_queue,并监听5条response_queue,监听类(消费者,相当于springboot-amqp starter中的@RabbitListener)为ResponseMapConsumer,监听方法为listenResponseMap(相当于@RabbitHandler)。将请求Json数据requestMap发送到请求队列request_queue i(1 ~ 5),并监听response_queue 1 - 5,只要监听到response queue i,能通过UUID和序列号i的对应校验,就将监听得到的SAP响应Json数据返回给前端或BPM的HttpClient。

applicationContext.xml
说明:
首先使用context:component-scan扫描到com.jake下所有被上下文注解标记的类,如(@Controller、@Service、@Repository、@Component等),context:annotation-config用于开启上下文注解,与context:component-scan功能重复。其中,rabbit:queue用于自动创建队列;message-converter使用了Jackson2JsonMessageConverter类,用于传递Json数据(本项目中暂未使用);rabbit-listener中使用queue-names属性监听了多条队列,队列名之间以逗号分隔;ref为消费者类名在上下文注解中的别名(默认类名首字母小写),method为其中处理监听队列携带数据的具体方法(参数与队列携带参数类型相同)。由于没有使用rabbit:binding标签,所以所有创建的rabbit消息队列都被绑定到RabbitMQ服务器默认的直连交换机AMQP Default上。

 <context:component-scan base-package="com.jake"/>
 <!--<context:annotation-config/>-->
 <rabbit:connection-factory
         id="rabbitConnectionFactory" host="127.0.0.1"
         username="guest" password="guest" port="5672"/>
 <rabbit:admin connection-factory="rabbitConnectionFactory"/>
 <rabbit:queue id="requestQueue1" name="REQUEST_QUEUE1"/>
 <rabbit:queue id="requestQueue2" name="REQUEST_QUEUE2"/>
 <rabbit:queue id="requestQueue3" name="REQUEST_QUEUE3"/>
 <rabbit:queue id="requestQueue4" name="REQUEST_QUEUE4"/>
 <rabbit:queue id="requestQueue5" name="REQUEST_QUEUE5"/>
 <bean id="jackson2JsonMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"/>
 <rabbit:template id="rabbitTemplate" connection-factory="rabbitConnectionFactory" message-converter="jackson2JsonMessageConverter"/>
 <rabbit:listener-container message-converter="jackson2JsonMessageConverter">
     <rabbit:listener queue-names="RESPONSE_QUEUE1, RESPONSE_QUEUE2, RESPONSE_QUEUE3, RESPONSE_QUEUE4, RESPONSE_QUEUE5"
                      ref="responseMapConsumer" method="listenResponseMap"/>
 </rabbit:listener-container>

生产者

package com.jake.apirabbitmvc.producer;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Map;

@Service
public class RequestMapProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendRequestMap(String queueName, Map<String, Object> requestMap) {
        rabbitTemplate.convertAndSend(queueName, requestMap);
    }

}

消费者

package com.jake.apirabbitmvc.consumer;

import lombok.Getter;
import lombok.Setter;
import org.springframework.stereotype.Service;

import java.util.Map;

@Service
public class ResponseMapConsumer {

    @Getter
    @Setter
    private Map<String, Object> responseMap;

    public void listenResponseMap(Map<String, Object> responseMap) {
        setResponseMap(responseMap);
    }

}

控制层
调用生产者的sendRequestMap方法发送请求Json数据,调用消费者的getResponseMap方法实时获取响应Json数据。

package com.aac.apirabbitmvc.controller;

import com.aac.apirabbitmvc.constant.Queues;
import com.aac.apirabbitmvc.consumer.ResponseMapConsumer;
import com.aac.apirabbitmvc.producer.RequestMapProducer;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;
import java.util.UUID;

@RestController
@RequestMapping(value = "/rfc")
public class BpmToRfcController {

    @Autowired
    private RequestMapProducer requestMapProducer;

    @Autowired
    private ResponseMapConsumer responseMapConsumer;

    @RequestMapping(value = "/json")
    public Map<String, Object> getResponseMap(@RequestBody Map<String, Object> requestMap) throws InterruptedException {
        String requestID = UUID.randomUUID().toString();
        int reqQNo = RandomUtils.nextInt(1, 6);
        requestMap.put("requestID", requestID);
        requestMap.put("reqQNo", reqQNo);
        requestMapProducer.sendRequestMap(Queues.REQUEST_QUEUE + reqQNo, requestMap);
        Map<String, Object> responseMap = responseMapConsumer.getResponseMap();
        // 不断获取@RabbitListener监听到的响应数据,响应数据不为NULL时退出循环。
        while (responseMap == null) {
            Thread.sleep(100);
            responseMap = responseMapConsumer.getResponseMap();
        }
        String responseID = (String) responseMap.get("responseID");
        int resQNo = (int) responseMap.get("resQNo");
        // 不断获取@RabbitListener监听到的响应数据,当标记值相同时退出循环。
        while (!(StringUtils.equals(requestID, responseID) && reqQNo == resQNo)) {
            Thread.sleep(100);
            responseMap = responseMapConsumer.getResponseMap();
            responseID = (String) responseMap.get("responseID");
            resQNo = (int) responseMap.get("resQNo");
        }
        return responseMap;
    }

}

项目二:rfc-rabbit-mvc,生产responseQueues,消费requestQueues

创建5条response_queue,并监听5条request_queue,监听类(消费者,相当于springboot-amqp starter中的@RabbitListener)为RequestMapConsumer,监听方法为listenRequestMap(相当于@RabbitHandler)。只要监听到request_queue 1 - 5中的任意一条(假设为i),就获取其中的请求Json数据,通过其中的参数调用RFC接口,获取SAP的响应Json数据responseMap,将该Json数据发送到response_queue i(1 ~ 5)。

applicationContext.xml

<context:component-scan base-package="com.jake"/>
<!--<context:annotation-config/>-->
<rabbit:connection-factory
        id="rabbitConnectionFactory" host="127.0.0.1"
        username="guest" password="guest" port="5672"/>
<rabbit:admin connection-factory="rabbitConnectionFactory"/>
<rabbit:queue id="responseQueue1" name="RESPONSE_QUEUE1"/>
<rabbit:queue id="responseQueue2" name="RESPONSE_QUEUE2"/>
<rabbit:queue id="responseQueue3" name="RESPONSE_QUEUE3"/>
<rabbit:queue id="responseQueue4" name="RESPONSE_QUEUE4"/>
<rabbit:queue id="responseQueue5" name="RESPONSE_QUEUE5"/>
<bean id="jackson2JsonMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"/>
<rabbit:template id="rabbitTemplate" connection-factory="rabbitConnectionFactory" message-converter="jackson2JsonMessageConverter"/>
<rabbit:listener-container message-converter="jackson2JsonMessageConverter">
    <rabbit:listener queue-names="REQUEST_QUEUE1, REQUEST_QUEUE2, REQUEST_QUEUE3, REQUEST_QUEUE4, REQUEST_QUEUE5"
                     ref="requestMapConsumer" method="listenRequestMap"/>
</rabbit:listener-container>

生产者

package com.jake.rfcrabbitmvc.producer;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Map;

@Service
public class RequestMapProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendResponseMap(String queueName, Map<String, Object> responseMap) {
        rabbitTemplate.convertAndSend(queueName, responseMap);
    }

}

消费者

package com.jake.rfcrabbitmvc.consumer;

import com.jake.rfcrabbitmvc.constant.Queues;
import com.jake.rfcrabbitmvc.producer.RequestMapProducer;
import com.jake.rfcrabbitmvc.service.BpmToSapService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Map;

@Service
public class RequestMapConsumer {

    @Autowired
    private RequestMapProducer requestMapProducer;

    @Autowired
    private BpmToSapService bpmToSapService;

    public void listenRequestMap(Map<String, Object> requestMap) {
        Map<String, Object> responseMap = bpmToSapService.getSapJson(requestMap);
        String requestID = (String) requestMap.get("requestID");
        int reqQNo = (int) requestMap.get("reqQNo");
        responseMap.put("responseID", requestID);
        responseMap.put("resQNo", reqQNo);
        requestMapProducer.sendResponseMap(Queues.RESPONSE_QUEUE + reqQNo, responseMap);
    }

}

根据请求Json数据获取SAP响应Json数据的bpmToSapService.getSapJson(requestMap)方法参考我的博客:《访问SAP统一RFC连接接口(RESTFUL风格)》

三、发布war包至Linux

在localhost使用postman测试,能够正常获取SAP的响应Json数据后,将两个项目的war包上传至Linux服务器Tomcat的webapps的目录下,bin目录执行./startup.sh,运行项目。
访问linux-server-ip:8080可以访问Tomcat主页。
携带请求Json数据访问linux-server-ip:8080/api-rabbit-mvc/rfc/json.action无法获取响应Json数据,分析可能的原因是当响应Json数据为null时,控制层就不返回数据。由此可以推断出api项目无法监听到rfc项目返回的Json数据。
携带请求Json数据直接访问linux-server-ip:8080/rfc-rabbit-mvc/rfc/json.action,发现报类似以下的错误:ClassNotFound:JCoConnection does not exist,明显缺少了sapjco3.jar和libsapjco3.so文件,百度谷歌搜索:sapjco3 linux,找到在Linux环境下配置有关sapjco的环境变量的方法。
参考博客:https://tw.saowen.com/a/47f989f244ed24d772d18b69c08feee2015966eb4b1ecbafbb6e22da32a8bae3
如果已经配置好了,依然报错,那么执行source /etc/profile使环境变量重新生效即可。
再访问linux-server-ip:8080/rfc-rabbit-mvc/rfc/json.action就可以获取Json数据了。

四、记录SpringBoot和SpringMVC项目创建的消息队列不通用的问题

4.1 背景

如果Rabbit服务器上存在着由SpringBoot-@Bean注解形式(SpringMVC-XML形式)项目创建的Rabbit消息队列,那么这些消息队列就不能被SpringMVC(SpringBoot)所消费。

4.2 暂时的解决方法

目前对这个问题没有深究,暂时的解决方法是如果希望消息队列是供SpringMVC项目使用的,但却是由SpringBoot项目创建的,那么只有在RabbitMQ的后台管理页面上删除消息队列,重启Tomcat(重启SpringMVC-RabbitMQ项目)已创建供SpringMVC项目使用的消息队列。

<think>我们之前已经配置了异步回调,现在用户要求使用同步回调处理确认结果。在RabbitMQ的生产者确认机制中,同步确认意味着在发送消息后阻塞当前线程直到收到Broker的确认。Spring AMQP提供了`RabbitTemplate`的`waitForConfirms`或`waitForConfirmsOrDie`方法,但通常不推荐在同步确认中使用,因为这会严重影响性能。 不过,在Spring AMQP中,我们可以通过配置`RabbitTemplate`来实现同步确认。注意:同步确认会阻塞发送线程,直到收到确认(或超时)。因此,在高并发场景下需谨慎使用。 以下是修改后的XML配置和代码实现: ### 1. XML配置调整(rabbitmq-context.xml) ```xml <?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: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/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd"> <!-- 加载RabbitMQ配置参数 --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:properties/rabbitmq.properties"/> </bean> <!-- 配置连接工厂(启用生产者确认) --> <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}" port="${rabbitmq.port}" username="${rabbitmq.username}" password="${rabbitmq.password}" virtual-host="${rabbitmq.virtual-host}" publisher-confirms="true" <!-- 关键:启用确认机制 --> publisher-returns="true"/> <!-- 启用消息返回 --> <!-- RabbitAdmin用于声明队列/交换机 --> <rabbit:admin connection-factory="connectionFactory"/> <!-- 声明队列 --> <rabbit:queue name="orderQueue" durable="true"/> <!-- 声明直连交换机 --> <rabbit:direct-exchange name="orderExchange"> <rabbit:bindings> <rabbit:binding queue="orderQueue" key="order.route"/> </rabbit:bindings> </rabbit:direct-exchange> <!-- 配置RabbitTemplate(关键:不使用异步回调,改为同步等待) --> <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory" exchange="orderExchange" routing-key="order.route" mandatory="true"> <!-- 确保消息可路由,触发return机制 --> </rabbit:template> </beans> ``` ### 2. 生产者实现(同步确认) ```java public class RmqProducer { @Resource private RabbitTemplate rabbitTemplate; public void sendOrderMessage(Order order) throws Exception { // 获取连接工厂 ConnectionFactory connectionFactory = rabbitTemplate.getConnectionFactory(); // 创建连接和通道 Connection connection = connectionFactory.createConnection(); Channel channel = connection.createChannel(false); // 非事务通道 try { // 开启发布者确认模式(如果还没有开启,但XML中已经配置了,所以这里可以省略) // 将通道设置为确认模式 channel.confirmSelect(); // 发送消息 String exchange = "orderExchange"; String routingKey = "order.route"; channel.basicPublish(exchange, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, new ObjectMapper().writeValueAsBytes(order)); // 同步等待确认(阻塞当前线程直到收到确认或超时) boolean confirmed = channel.waitForConfirms(5000); // 5秒超时 if (confirmed) { System.out.println("✅ 消息已确认"); } else { System.out.println("❌ 消息确认超时或失败"); // 处理失败情况 } } finally { // 关闭通道和连接 channel.close(); connection.close(); } } } ``` ### 注意: 1. **性能影响**:同步确认会阻塞发送线程,导致吞吐量下降。在需要高并发的场景下,异步确认是更好的选择[^1]。 2. **资源管理**:每次发送都创建新的连接和通道开销很大,可以考虑使用连接池(如`SimpleConnectionFactory`)或复用通道(但注意通道非线程安全)。 3. **超时处理**:`waitForConfirms`方法可以设置超时时间,避免永久阻塞。 4. **事务模式**:如果同时使用事务(`channel.txSelect()`),则不能使用发布者确认模式。两者是互斥的[^2]。 ### 替代方案:使用RabbitTemplate的同步确认 Spring AMQP的`RabbitTemplate`本身不直接提供同步确认的API,但我们可以通过以下方式间接实现: ```java public class RmqProducer { @Resource private RabbitTemplate rabbitTemplate; public void sendOrderMessage(Order order) { // 创建CorrelationData CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString()); // 发送消息 rabbitTemplate.convertAndSend("orderExchange", "order.route", order, correlationData); // 同步等待确认(通过Future) Confirm confirm = correlationData.getFuture().get(5, TimeUnit.SECONDS); if (confirm.isAck()) { System.out.println("✅ 消息已确认"); } else { System.out.println("❌ 消息被Broker拒绝"); } } } ``` ### 配置调整: 为了使用上述方式,我们需要在XML配置`confirm-callback`(但不需要实现类)因为`RabbitTemplate`内部会使用`CorrelationData`的`Future`: ```xml <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory" exchange="orderExchange" routing-key="order.route" confirm-callback="rabbitTemplate" <!-- 使用RabbitTemplate自身作为回调 --> mandatory="true"> </rabbit:template> ``` 但是,这种方式实际上还是异步的,我们通过`Future.get()`将其转换为同步等待。因此,在XML中仍然需要配置`confirm-callback`(尽管我们并不需要自定义回调逻辑)。 ### 总结: - **纯同步模式**:使用原生Channel的`waitForConfirms`,但需要自行管理连接和通道。 - **半同步模式**:使用`RabbitTemplate`和`CorrelationData`的`Future`,通过`get()`方法阻塞等待。 根据业务需求选择合适的方式。如果对性能要求高,建议使用异步回调;如果对消息可靠性要求极高且可以容忍一定的性能损失,可以选择同步方式。 §§相关问题§§ 1. RabbitMQ同步确认和异步确认的性能差异有多大? 2. 如何优化RabbitMQ同步确认模式下的连接管理? 3. RabbitMQ生产者确认机制与事务机制有何区别? 4. 在Spring AMQP中如何实现同步消息返回(不可路由消息的处理)? 5. 如何监控RabbitMQ同步确认的超时率? [^1]: 开启持久化和确认机制会增加负载,影响性能 [^2]: RabbitMQ队列和交换机默认不持久化,重启后消失 [^3]: Spring的控制反转和依赖注入概念</think>### Spring MVC整合RabbitMQ生产者同步确认机制(XML配置) 以下是完整的同步确认配置方案,通过XML配置实现生产者同步确认机制: #### 1. 核心XML配置rabbitmq-sync.xml) ```xml <?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: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/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd"> <!-- 加载配置参数 --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:rabbitmq.properties"/> </bean> <!-- 连接工厂(启用生产者确认) --> <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}" port="${rabbitmq.port}" username="${rabbitmq.username}" password="${rabbitmq.password}" virtual-host="${rabbitmq.virtual-host}" publisher-confirms="true" publisher-returns="true"/> <!-- RabbitAdmin --> <rabbit:admin connection-factory="connectionFactory"/> <!-- 队列声明 --> <rabbit:queue name="syncConfirmQueue" durable="true"/> <!-- 直连交换机 --> <rabbit:direct-exchange name="syncExchange"> <rabbit:bindings> <rabbit:binding queue="syncConfirmQueue" key="sync.route"/> </rabbit:bindings> </rabbit:direct-exchange> <!-- RabbitTemplate配置(同步确认模式) --> <rabbit:template id="syncRabbitTemplate" connection-factory="connectionFactory" exchange="syncExchange" routing-key="sync.route" mandatory="true" confirm-callback="syncConfirmCallback" return-callback="syncReturnCallback"> <!-- 禁用异步监听器 --> <rabbit:reply-listener concurrency="0"/> </rabbit:template> <!-- 同步确认回调处理器 --> <bean id="syncConfirmCallback" class="com.example.SyncConfirmCallback"/> <bean id="syncReturnCallback" class="com.example.SyncReturnCallback"/> </beans> ``` #### 2. 同步回调处理器实现 ```java // 同步确认回调 public class SyncConfirmCallback implements RabbitTemplate.ConfirmCallback { @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { // 同步处理确认结果(直接在当前线程执行) if (ack) { System.out.println("[同步确认] 消息已送达Broker: " + correlationData.getId()); } else { System.err.println("[同步确认失败] ID: " + correlationData.getId() + " 原因: " + cause); // 同步处理失败逻辑 handleSendFailure(correlationData); } } } // 同步返回回调(路由失败) public class SyncReturnCallback implements RabbitTemplate.ReturnCallback { @Override public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) { String msgId = message.getMessageProperties().getCorrelationId(); System.err.println("[同步返回] 消息路由失败 ID: " + msgId + " | 原因: " + replyText); // 同步处理不可路由的消息 } } ``` #### 3. 生产者实现(同步确认) ```java public class SyncProducer { @Autowired private RabbitTemplate syncRabbitTemplate; public void sendWithSyncConfirm(String message) { CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString()); // 发送消息(同步等待确认) syncRabbitTemplate.invoke(template -> { template.convertAndSend( "syncExchange", "sync.route", message, msg -> { msg.getMessageProperties().setCorrelationId(correlationData.getId()); return msg; }, correlationData ); // 同步等待确认结果(最大等待5秒) return template.waitForConfirms(5000); }, (t, success) -> { if (!success) { System.err.println("⚠️ 同步确认超时: " + correlationData.getId()); } return success; }); } } ``` ### 关键配置说明: 1. **同步确认核心机制**: - `publisher-confirms="true"`:启用Broker确认 - `confirm-callback`:绑定同步确认处理器 - `reply-listener concurrency="0"`:禁用异步监听器 - `waitForConfirms()`:阻塞等待确认结果 2. **同步 vs 异步对比**: ```mermaid graph LR A[生产者] --> B[发送消息] B --> C{确认方式} C -->|同步| D[阻塞等待结果] C -->|异步| E[继续执行其他任务] D --> F[立即处理结果] E --> G[回调处理结果] ``` 3. **性能考量**: - 同步确认会阻塞发送线程,降低吞吐量(约30-50%的性能损失)[^1] - 适合对可靠性要求极高且并发量不高的场景(如金融交易) - 高并发场景推荐使用异步确认 ### 超时处理策略: ```java // 在生产者中添加超时重试逻辑 public void sendWithRetry(String message, int maxRetries) { int attempt = 0; while (attempt < maxRetries) { try { sendWithSyncConfirm(message); return; // 发送成功则退出 } catch (Exception e) { attempt++; System.err.println("发送失败,尝试重试 (" + attempt + "/" + maxRetries + ")"); Thread.sleep(1000 * attempt); // 指数退避 } } throw new RuntimeException("消息发送失败,超过最大重试次数"); } ``` ### 应用场景建议: 1. **同步确认适用场景**: - 需要严格保证消息送达的金融交易 - 低频但关键的系统间通信 - 测试环境验证消息可靠性 2. **异步确认适用场景**: - 高吞吐量日志处理 - 电商订单等允许少量丢失的场景 - 实时消息推送系统 > **注意**:同步确认会显著降低系统吞吐量,建议只在必要时使用。对于大多数应用场景,异步确认配合适当的重试机制是更优选择[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值