自动补偿机制(消费者)及幂等问题

RocketMQ 消息重试与补偿机制 消息重试与补偿是确保消息最终被正确处理的一种机制。当消息消费失败时,系统会自动进行重试;如果重试多次仍然失败,可以通过补偿机制进行手动处理或重新执行相关操作。RocketMQ 的消息重试与补偿机制通过捕获消费失败的消息,提供了一种可靠的消息处理和错误恢复手段。在实际应用中,通过合理配置和使用重试与补偿机制,可以有效提升系统的稳定性和数据一致性。未来的开发中,充分利用消息重试与补偿机制,可以更好地应对复杂的业务需求和异常处理场景。 阅读详情

自动补偿机制(消费者)及幂等问题

如果消费者 运行时候 报错了

package com.toov5.msg.SMS;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component 
@RabbitListener(queues="fanout_sms_queue")   
public class SMSConsumer {
    
    @RabbitHandler  
   public void process(String mString) {
       System.out.println("短信消费者获取生产者消息msg"+mString);
       int i = 1/0;
   }
}

当生产者投递消息后:

消费者会不停的进行打印:

 

 消息一直没有被消费

 

原因 Rabbitmq 默认情况下 如果消费者程序出现异常情况 会自动实现补偿机制  也就是 重试机制

 

@RabbitListener底层使用AOP进行拦截,如果程序没有抛出异常,自动提交事务。 如果Aop使用异常通知 拦截获取异常信息的话 , 自动实现补偿机制,该消息会一直缓存在Rabbitmq服务器端进行重放,一直重试到不抛出异常为准。

 可以修改重试策略

 一般来说默认5s重试一次,

消费者配置:

 listener:
      simple:
        retry:
        ####开启消费者重试
          enabled: true
         ####最大重试次数(默认无数次)
          max-attempts: 5
        ####重试间隔次数
          initial-interval: 3000

效果: 充实5次 不行就放弃了

 

 

 

 

 MQ重试机制机制 需要注意的问题

  

如何合适选择重试机制

 

情况1:  消费者获取到消息后,调用第三方接口,但接口暂时无法访问,是否需要重试?      需要重试   别人的问题不是我自己的问题

 

情况2:  消费者获取到消息后,抛出数据转换异常,是否需要重试?                                      不需要重试   充实一亿次也是如此 木有必要  需要发布版本解决

总结:对于情况2,如果消费者代码抛出异常是需要发布新版本才能解决的问题,那么不需要重试,重试也无济于事。应该采用 日志记录+定时任务job健康检查+人工进行补偿

           把错误记录在日志里面,通过定时Job去自动的补偿,或通过人工去补偿。 

 

 传统的HTTP请求 如果失败了 没法自动重试  当然自己可以写个循环实现

 MQ完全自己自带的

 

 

情况2的拓展延申:

    将之前的案例改为   邮件消费者 调用邮件第三方接口 

 

   伪代码:

    在consumer 中 调用接口后 判断返回值  由于RabbitMQ 在消费者异常时候 会进行重试机制 进行补偿 

    所以可以抛出个异常 来实现

   

Consumer:

              String result   =   template.Email();

             if(result == null){

                   throw new Exception("调用第三方邮件服务器接口失败!");

               }

 

 

producer:

 

pom:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.itmayiedu</groupId>
    <artifactId>rabbitmq_producer_springboot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>
    <dependencies>

        <!-- springboot-web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 添加springboot对amqp的支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <!--fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.49</version>
        </dependency>
    </dependencies>
</project>

config:

package com.itmayiedu.rabbitmq.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

//Fanout 类型 发布订阅模式
@Component
public class FanoutConfig {

    // 邮件队列
    private String FANOUT_EMAIL_QUEUE = "fanout_email_queue";

    // 短信队列
    private String FANOUT_SMS_QUEUE = "fanout_sms_queue";
    // fanout 交换机
    private String EXCHANGE_NAME = "fanoutExchange";

    // 1.定义邮件队列
    @Bean
    public Queue fanOutEamilQueue() {
        return new Queue(FANOUT_EMAIL_QUEUE);
    }

    // 2.定义短信队列
    @Bean
    public Queue fanOutSmsQueue() {
        return new Queue(FANOUT_SMS_QUEUE);
    }

    // 2.定义交换机
    @Bean
    FanoutExchange fanoutExchange() {
        return new FanoutExchange(EXCHANGE_NAME);
    }

    // 3.队列与交换机绑定邮件队列
    @Bean
    Binding bindingExchangeEamil(Queue fanOutEamilQueue, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(fanOutEamilQueue).to(fanoutExchange);
    }

    // 4.队列与交换机绑定短信队列
    @Bean
    Binding bindingExchangeSms(Queue fanOutSmsQueue, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(fanOutSmsQueue).to(fanoutExchange);
    }
}

 

 Producer:

package com.itmayiedu.rabbitmq;

import java.util.UUID;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSONObject;

@Component
public class FanoutProducer {
    @Autowired
    private AmqpTemplate amqpTemplate;

    public void send(String queueName) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("email", "xx@163.com");
        jsonObject.put("timestamp", System.currentTimeMillis());
        String jsonString = jsonObject.toJSONString();
        System.out.println("jsonString:" + jsonString);
        // 设置消息唯一id 保证每次重试消息id唯一
        /*Message message = MessageBuilder.withBody(jsonString.getBytes())
                .setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8")
                .setMessageId(UUID.randomUUID() + "").build();*/
        amqpTemplate.convertAndSend(queueName, jsonString);
    }
}

Controller:

package com.itmayiedu.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.itmayiedu.rabbitmq.FanoutProducer;

@RestController
public class ProducerController {
    @Autowired
    private FanoutProducer fanoutProducer;

    @RequestMapping("/sendFanout")
    public String sendFanout(String queueName) {
        fanoutProducer.send(queueName);
        return "success";
    }
}

yml:

spring:
  rabbitmq:
  ####连接地址
    host: 192.168.91.6
   ####端口号   
    port: 5672
   ####账号 
    username: admin
   ####密码  
    password: admin
   ### 地址
    virtual-host: /admin_toov5

启动类:

package com.itmayiedu;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AppProducer {

    public static void main(String[] args) {
        SpringApplication.run(AppProducer.class, args);
    }

}

 

Consumer:

pom:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.itmayiedu</groupId>
    <artifactId>rabbitmq_consumer_springboot</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>
    <dependencies>

        <!-- springboot-web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 添加springboot对amqp的支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <!--fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.49</version>
        </dependency>

    </dependencies>
</project>

 

 utils:

package com.itmayiedu.rabbitmq.utils;

import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

/**
 * HttpClient4.3工具类
 * 
 * @author hang.luo
 */
public class HttpClientUtils {
    private static Logger logger = LoggerFactory.getLogger(HttpClientUtils.class); // 日志记录

    private static RequestConfig requestConfig = null;

    static {
        // 设置请求和传输超时时间
        requestConfig = RequestConfig.custom().setSocketTimeout(2000).setConnectTimeout(2000).build();
    }

    /**
     * post请求传输json参数
     * 
     * @param url
     *            url地址
     * @param json
     *            参数
     * @return
     */
    public static JSONObject httpPost(String url, JSONObject jsonParam) {
        // post请求返回结果
        CloseableHttpClient httpClient = HttpClients.createDefault();
        JSONObject jsonResult = null;
        HttpPost httpPost = new HttpPost(url);
        // 设置请求和传输超时时间
        httpPost.setConfig(requestConfig);
        try {
            if (null != jsonParam) {
                // 解决中文乱码问题
                StringEntity entity = new StringEntity(jsonParam.toString(), "utf-8");
                entity.setContentEncoding("UTF-8");
                entity.setContentType("application/json");
                httpPost.setEntity(entity);
            }
            CloseableHttpResponse result = httpClient.execute(httpPost);
            // 请求发送成功,并得到响应
            if (result.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                String str = "";
                try {
                    // 读取服务器返回过来的json字符串数据
                    str = EntityUtils.toString(result.getEntity(), "utf-8");
                    // 把json字符串转换成json对象
                    jsonResult = JSONObject.parseObject(str);
                } catch (Exception e) {
                    logger.error("post请求提交失败:" + url, e);
                }
            }
        } catch (IOException e) {
            logger.error("post请求提交失败:" + url, e);
        } finally {
            httpPost.releaseConnection();
        }
        return jsonResult;
    }

    /**
     * post请求传输String参数 例如:name=Jack&sex=1&type=2
     * Content-type:application/x-www-form-urlencoded
     * 
     * @param url
     *            url地址
     * @param strParam
     *            参数
     * @return
     */
    public static JSONObject httpPost(String url, String strParam) {
        // post请求返回结果
        CloseableHttpClient httpClient = HttpClients.createDefault();
        JSONObject jsonResult = null;
        HttpPost httpPost = new HttpPost(url);
        httpPost.setConfig(requestConfig);
        try {
            if (null != strParam) {
                // 解决中文乱码问题
                StringEntity entity = new StringEntity(strParam, "utf-8");
                entity.setContentEncoding("UTF-8");
                entity.setContentType("application/x-www-form-urlencoded");
                httpPost.setEntity(entity);
            }
            CloseableHttpResponse result = httpClient.execute(httpPost);
            // 请求发送成功,并得到响应
            if (result.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                String str = "";
                try {
                    // 读取服务器返回过来的json字符串数据
                    str = EntityUtils.toString(result.getEntity(), "utf-8");
                    // 把json字符串转换成json对象
                    jsonResult = JSONObject.parseObject(str);
                } catch (Exception e) {
                    logger.error("post请求提交失败:" + url, e);
                }
            }
        } catch (IOException e) {
            logger.error("post请求提交失败:" + url, e);
        } finally {
            httpPost.releaseConnection();
        }
        return jsonResult;
    }

    /**
     * 发送get请求
     * 
     * @param url
     *            路径
     * @return
     */
    public static JSONObject httpGet(String url) {
        // get请求返回结果
        JSONObject jsonResult = null;
        CloseableHttpClient client = HttpClients.createDefault();
        // 发送get请求
        HttpGet request = new HttpGet(url);
        request.setConfig(requestConfig);
        try {
            CloseableHttpResponse response = client.execute(request);

            // 请求发送成功,并得到响应
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                // 读取服务器返回过来的json字符串数据
                HttpEntity entity = response.getEntity();
                String strResult = EntityUtils.toString(entity, "utf-8");
                // 把json字符串转换成json对象
                jsonResult = JSONObject.parseObject(strResult);
            } else {
                logger.error("get请求提交失败:" + url);
            }
        } catch (IOException e) {
            logger.error("get请求提交失败:" + url, e);
        } finally {
            request.releaseConnection();
        }
        return jsonResult;
    }

}

consumer:

package com.itmayiedu.rabbitmq;

import java.util.Map;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSONObject;
import com.itmayiedu.rabbitmq.utils.HttpClientUtils;
import com.rabbitmq.client.Channel;

//邮件队列
@Component
public class FanoutEamilConsumer {
     @RabbitListener(queues = "fanout_email_queue")
     public void process(String msg) throws Exception {
     System.out.println("邮件消费者获取生产者消息msg:" + msg);
     JSONObject jsonObject = JSONObject.parseObject(msg);
     // 获取email参数
     String email = jsonObject.getString("email");
     // 请求地址
     String emailUrl = "http://127.0.0.1:8083/sendEmail?email=" + email;
     JSONObject result = HttpClientUtils.httpGet(emailUrl);
     if (result == null) {
     // 因为网络原因,造成无法访问,继续重试
     throw new Exception("调用接口失败!");
     }
     System.out.println("执行结束....");
    
     }
}

yml:

spring:
  rabbitmq:
  ####连接地址
    host: 192.168.91.6
   ####端口号   
    port: 5672
   ####账号 
    username: admin
   ####密码  
    password: admin
   ### 地址
    virtual-host: /admin_toov5
    listener: 
      simple:
        retry:
        ####开启消费者异常重试
          enabled: true
         ####最大重试次数
          max-attempts: 5
        ####重试间隔次数
          initial-interval: 2000
       

server:
  port: 8081

 

启动类:

package com.itmayiedu.rabbitmq;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AppConsumer {

    public static void main(String[] args) {
        SpringApplication.run(AppConsumer.class, args);
    }

}

 

 邮件服务器:

package com.mayikt.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class MsgController {

    // 模拟第三方发送邮件
    @RequestMapping("/sendEmail")
    public Map<String, Object> sendEmail(String email) {
        System.out.println("开始发送邮件:" + email);
        Map<String, Object> result = new HashMap<String, Object>();
        result.put("code", "200");
        result.put("msg", "发送邮件成功..");
        System.out.println("发送邮件成功");
        return result;
    }

    public static void main(String[] args) {
        SpringApplication.run(MsgController.class, args);
    }

}

yml:

server:
  port: 8083

pom:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.mayikt</groupId>
    <artifactId>mayikt_sms</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>
    <dependencies>

        <!-- springboot-web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 添加springboot对amqp的支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <!--fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.49</version>
        </dependency>

    </dependencies>
</project>

在没有启动邮件服务器时候,消费者 调用接口失败 会一直重试 重试五次

在此期间 如果启动成功 则重试成功 不再重试  不再进行补偿机制

 

 

消费者如果保证消息幂等性,不被重复消费

 

产生原因:网络延迟传输中,或者消费出现异常或者是消费延迟,会造成进行MQ重试进行重试补偿机制,在重试过程中,可能会造成重复消费。

 

解决办法:

使用全局MessageID判断消费方使用同一个,解决幂等性。

  

   只要重试过程中,判断如果已经走完了 不能再继续走 继续执行了

   MQ消费者的幂等行的解决 一般使用全局ID  或者写个唯一标识比如时间戳   或者UUID  或者订单号

   

  改进:

  producer:

 添加:

 

// 设置消息唯一id 保证每次重试消息id唯一  
        Message message = MessageBuilder.withBody(jsonString.getBytes())
                .setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8")
                .setMessageId(UUID.randomUUID() + "").build(); //消息id设置在请求头里面 用UUID做全局ID 
        amqpTemplate.convertAndSend(queueName, message);

 

 全部代码:

  

package com.itmayiedu.rabbitmq;

import java.util.UUID;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSONObject;

@Component
public class FanoutProducer {
    @Autowired
    private AmqpTemplate amqpTemplate;

    public void send(String queueName) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("email", "xx@163.com");
        jsonObject.put("timestamp", System.currentTimeMillis());
        String jsonString = jsonObject.toJSONString();
        System.out.println("jsonString:" + jsonString);
        // 设置消息唯一id 保证每次重试消息id唯一  
        Message message = MessageBuilder.withBody(jsonString.getBytes())
                .setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8")
                .setMessageId(UUID.randomUUID() + "").build(); //消息id设置在请求头里面 用UUID做全局ID 
        amqpTemplate.convertAndSend(queueName, message);
    }
}

 

 同样的 消费者也需要修改:

 方法参数类型为 Message  然后可以获取这个ID 然后可以进行业务逻辑操作

 @RabbitListener(queues = "fanout_email_queue")
     public void process(Message message) throws Exception {
     // 获取消息Id
     String messageId = message.getMessageProperties().getMessageId();  //id获取之
     String msg = new String(message.getBody(), "UTF-8"); //消息内容获取之
     System.out.println("-----邮件消费者获取生产者消息-----------------" + "messageId:" + messageId + ",消息内容:" +
     msg);
     if (messageId == null) {
            return;
        }
     JSONObject jsonObject = JSONObject.parseObject(msg);
     // 获取email参数
     String email = jsonObject.getString("email");
     // 请求地址
     String emailUrl = "http://127.0.0.1:8083/sendEmail?email=" + email;
     JSONObject result = HttpClientUtils.httpGet(emailUrl);
     if (result == null) {
     // 因为网络原因,造成无法访问,继续重试
     throw new Exception("调用接口失败!");
     }
     System.out.println("执行结束....");
     //messId 的情况写入到redis 中  成功就修改为空
     }

 

 重试机制都是间隔性的  每次都是一个线程  单线程重试

 

 

 关于应答模式:

    Spring boot 中进行 AOP拦截 自动帮助做重试

    手动应答的话 ,如果不告诉服务器已经消费成功,则服务器不会删除 消息。告诉消费成功了才会删除。

    

消费者的yml加入:

 acknowledge-mode: manual 

    

spring:
  rabbitmq:
  ####连接地址
    host: 192.168.91.6
   ####端口号   
    port: 5672
   ####账号 
    username: admin
   ####密码  
    password: admin
   ### 地址
    virtual-host: /admin_toov5
    listener: 
      simple:
        retry:
        ####开启消费者异常重试
          enabled: true
         ####最大重试次数
          max-attempts: 5
        ####重试间隔次数
          initial-interval: 2000
        ####开启手动ack  
        acknowledge-mode: manual 

server:
  port: 8081

 

 

 开启模式之后:

   消费者参数需要加入:  @Headers Map<String, Object> headers, Channel channel

  代码逻辑最后面加入:

// // 手动ack
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
// 手动签收  告诉RabbitMQ 消费成功了  消息可以删除了
channel.basicAck(deliveryTag, false);  

 

代码如下:

@RabbitListener(queues = "fanout_email_queue")
    public void process(Message message, @Headers Map<String, Object> headers, Channel channel) throws Exception {
        // 获取消息Id
        String messageId = message.getMessageProperties().getMessageId();
        String msg = new String(message.getBody(), "UTF-8");
        System.out.println("邮件消费者获取生产者消息" + "messageId:" + messageId + ",消息内容:" + msg);
        JSONObject jsonObject = JSONObject.parseObject(msg);
        // 获取email参数
        String email = jsonObject.getString("email");
        // 请求地址
        String emailUrl = "http://127.0.0.1:8083/sendEmail?email=" + email;
        JSONObject result = HttpClientUtils.httpGet(emailUrl);
        if (result == null) {
            // 因为网络原因,造成无法访问,继续重试
            throw new Exception("调用接口失败!");
        }
        // // 手动ack
        Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
        // 手动签收
        channel.basicAck(deliveryTag, false);
        System.out.println("执行结束....");
    }
SpringBoot 服务调用异常重试补偿处理 1. 启动Demo01和Demo02项目 2. 请求 地址:127.0.0.1:8080/demo01/send/post 报文:{"sleepNum":5} 3. 测试目的 demo02 超时 demo02 无法访问 重试机制 超时、无法访问执行补偿操作 ----------------------------- 操作1. 传入的参数为demo02的sleep时间, demo01的请求超时时间为1秒, 这时候demo01出现超时异常, 直接进入补偿处理 操作2. 停止demo02, 请求demo01,出现网络异常, demo01会执行三次重试后在进行补偿处理 立即下载

相关推荐

PMSM单电阻采样避坑指南:如何解决非观测区电流重构难题(附ST专利分析)

本文深入解析了PMSM单电阻采样技术中的非观测区电流重构难题,并详细介绍了ST专利方案及移相法的工程实践。通过优化PWM调制策略和系统级调试方法,有效解决了相电流检测精度问题,为电机控制领域的工程师提供了实用的技术指南。

weixin_33737774的博客 632

RabbitMQ的消息补偿机制

文章目录 前言 一、pandas是什么? 二、使用步骤 1.引入库 2.读入数据 总结 前言 提示:这里可以添加本文要记录的大概内容: 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。 提示:以下是本篇文章正文内容,下面案例可供参考 一、pandas是什么? 示例:pandas 是基于NumPy 的一种工具,该工具是为了解决....

weixin_44399827的博客 1万+

MFC下使用C++操作Word文档

由于项目需求,在MFC环境下操作将数据和图片写入word文档,之前没有接触过,网上查阅了很多资料,终于把任务完成。理解的不是很透彻,但是这些东西应该够用了,写篇博客记录一下,以便以后再用到。 (1)VS环境下创建一个MFC应用程序,然后在项目上单击右键,选择 类向导-----添加类----类型库中的MFC类。在弹出的对话框中选择文件,位置选择office安装目录下的...

GIS_yuhua的博客 7579

99%的人都能看懂的MQ补偿机制

补偿机制的意义 假设有这样子一种场景:客户端请求 ---->空间结构微服务 ---->业主档案微服务 ---->房源微服务。这种调用链非常普遍。那么为什么需要考虑补偿机制呢? 一次跨机器的通信可能会经过DNS 服务,网卡、交换机、路由器、负载均衡等设备,这些设备都不一定是一直稳定的,在数据传输的整个过程中,只要任意一个环节出错,都会导致问题的产生。而在分布式场景中,一个完整的业务又是由多次跨机器通信组成的,所以产生问题的概率成倍数增加。 但是,这些问题并不完全代表真正的系统无法处理请求,所

程铭程铭你快成名的博客 4966

AWSIM模拟器 新增多相机

本文介绍了基于Unity6引擎对AWSIM自动驾驶模拟器的优化方案,主要针对多相机系统的扩展实现。作者通过修改EgoVehicle.cs和AutowareSimulationDemo.cs核心代码,将原单相机系统升级为5相机配置。关键技术点包括:1)使用List容器管理多个CameraSensor组件;2)完善相机初始化流程;3)配置5个相机UI显示窗口;4)修改ROS话题设置。优化后的系统可支持BEVFusion等多传感器融合算法的测试验证,为Autoware.universe 0.45.0及以上版本提供

W805698955的博客 831

新手必看!Unity中Tag和Layer的7个实际应用场景(含MOBA案例)

本文深入探讨Unity中Tag和Layer的7个实际应用场景,特别以MOBA游戏开发为例,详细解析如何利用LayerMask进行高效场景管理和碰撞检测优化。从双相机方案到射线检测的精准控制,帮助开发者掌握这些基础功能的进阶用法,提升游戏性能和开发效率。

weixin_28723171的博客 313

SpringBoot整合消息中间件 RabbitMQ 第 8 篇 —— 补偿机制(重试机制)、解决幂等性问题、手动确认消息

补偿机制(重试机制) RabbitMQ 队列服务器,对消息默认有个补偿机制。即当消费者没有成功消费消息时,会缓存消息,并且不断的把消息重新发给消费者,直到消息被消费掉(默认情况下会一直发,除非我们修改配置)。RabbitMQ 底层是使用 Spring AOP 进行拦截,如果消费者的@RabbitListener 函数报错,会自动触发重试机制。 我们来演示一下。我们使用之前的项目进行测试。代码在百度网盘连接:https://pan.baidu.com/s/1Izb0WbsuLCJJXlm445...

流放深圳 1923

进阶01:Labview与OPC UA设备通信

LabVIEW通过OPC UA实现高效设备通信方案 本文介绍了使用LabVIEW与OPC UA设备通信的完整实施方案。OPC UA协议具有跨平台兼容、高安全性和高效数据交换等优势。实施步骤包括:1)安装NI OPC UA工具包;2)搭建模拟测试环境,使用OPC UA Simulation Server创建测试变量;3)编写LabVIEW程序,实现连接服务器、事件订阅、数据读写等功能。重点展示了通过用户事件实现设备主动触发的机制,当模拟变量值改变时自动触发LabVIEW事件处理。该方案验证了OPC UA在工业

weixin_38637405的博客 1402

Rabbitmq消息补偿机制

8、Q3中的延迟消息延迟时间已到,被回调检查服务接收到,之后就拿着这条延迟消息在数据库中比对,如果比对成功,证明消费者接收到了生产者的业务消息并处理成功(如果不处理成功谁会傻了吧唧发送确认消息呢);2、自定义补偿机制。由于消息补偿机制的存在,可以更加有效保证消息可以被消费,但是带来的问题是可能某个消息执行的比较久,导致同一条消息再次被发送给了消费者,而前一条消息顺利执行完,这样一条消息就会被多次执行,所以消费者端的方法需要涉及成幂等性,也就是对于一条消息,无论被消费者消费几次,效果都是一样的。

C18298182575的博客 1211

一文掌握DrissionPage的详细使用

是一个基于 Python 的网页自动化工具,主要用于网页数据的抓取、自动化操作和测试。它结合了 Selenium 和 Requests 的优点,提供了更简洁、高效的 API,适用于各种网页自动化任务。DrissionPage 是一个功能强大且易于使用的网页自动化工具,适用于各种网页自动化任务。希望本文的 DrissionPage 的基本使用方法和一些高级功能能够帮助到你。

数据知道的博客 1万+

7、Python高光谱分析工具(PyHAT)简介

Python高光谱分析工具(PyHAT)是一个开源、跨平台的Python库及图形用户界面,用于行星光谱数据的可视化与分析。它支持多种高光谱传感器数据,如火星勘测轨道飞行器的CRISM、月船1号的M3以及SELENE Kaguya SP点光谱仪,提供预处理、衍生产品生成、统计学习和机器学习分析功能。PyHAT基于Pandas和NumPy构建,兼容Jupyter和QGIS环境,旨在解决现有光谱分析方法缺乏开源实现、难以探索性分析和算法分散的问题。其模块化架构涵盖数据输入/输出、通用光谱变换、测绘与点观测工具及高

aa123的博客 138

RabbitMQ 消息确认机制、补偿机制、消息幂等性实践

1. 场景 先看这么几个面试题: 如何保证消息的可靠性投递?即如何确定消息是否发送成功? 如果失败如何处理(补偿机制)? 如何保证消息不被重复消费?或者说,如何保证消息消费时的幂等性? 2. 消息的可靠性投递 消息确认 消息确认包括主要生产者发送确认和消费者接收确认,因为发送消息的过程中我们是无法确认消息是否能路由等,一旦消息丢失我们就无法处理,所以需要确认消息,避免消息丢失。 2.1 生产者确认 我们知道生产者与消费者完全隔离的,不做任何配置的情况下,生产者是不知道消息是否真正到达 RabbitMQ,

个人站点:javatv.net 3069

ADAS方案设计-CAN网络管理与休眠唤醒机制

文章摘要:本文介绍了车载控制系统的五种工作模式及其转换条件,包括睡眠模式、准备睡眠模式、就绪睡眠模式、正常工作模式和重复消息状态。系统唤醒条件包括接收到网络管理报文或电源ON档信号,休眠条件则需满足电源OFF档、功能完成等多重要求。针对下挂节点的网络管理报文发送规则,详细说明了不同状态下的报文发送策略,特别是在哨兵模式、OTA模式等特殊场景下的处理方式。系统在满足休眠条件后会立即停止发送下挂节点网络管理报文,确保电源管理的有效性。

xiandang8023的博客 325

RabbitMQ自动补偿机制(消费者)及幂等问题

如果消费者 运行时候 报错了 package com.toov5.msg.SMS; import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springfr...

weixin_30735745的博客 4360

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

85.RabbitMQ解决订单自动超时与消息幂等解决方案

可能抛出异常 算消费失败 重试5次之后: 重试: 不会重试: 6. 消息重试 7.代码 生产者: 消费者: 最后一次重试成功: 8、mq 确认消费 开启ack: 9、 数据库会再次插入,产生相同数据。 通过全局id来避免幂等性问题: ...

封神之路 1346

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

java消息幂等性实现_rabbitmq系列(三)消息幂等性处理

一、springboot整合rabbitmq我们需要新建两个工程,一个作为生产者,另一个作为消费者。在pom.xml中添加amqp依赖:org.springframework.bootspring-boot-starter-amqp在application.yml文件中添加rabbitmq的相关信息:spring:rabbitmq:# 连接地址host: 127.0.0.1# 端口port: 56...

weixin_39844267的博客 505

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SpringBoot整合RabbitMQ重试机制及配置

RabbitMQ自动补偿机制触发:(多用于调用第三方接口) 1.当我们的消费者在处理我们的消息的时候,程序抛出异常情况下(默认无限次数重试) 2.应该对我们的消息重试设置间隔重试时间,比如消费失败最多只能重试5次,间隔3秒(防止重复消费,幂等问题) 如果重试5次,也就是15秒内重试还是失败情况下应该如何处理 1.默认情况下,重试多次还是失败的话,会自动删除该消息(消息可能会丢失) 解决思路: A:如果重试多次还是失败的情况下,最终存放到死信队列. B:采用表日志记录,消费失败错误的日志记录 后期人工自动对消

web13618542420的博客 1218

RabbitMQ:01消费者出现业务逻辑异常,自动补偿机制

一:消费者出现业务逻辑异常,自动补偿机制 问题还原 :消费者程序出现异常,服务器一直报错 问题原因 rabbitmq 默认情况下 如果消费者程序出现异常的情况下,会自动实现补偿机制 补偿(重试机制) 队列服务器 发送补偿请求 @RabbitListener 底层 使用Aop进行拦截,如果程序没有抛出异常,自动提交事务 如果Aop使用异常通知拦截 获取异常信息的话,自动实现补偿机制 ,该消息会缓存...

qq_25292419的博客 1832

RabbitMQ解决消费者补偿幂等问题(重复消费问题)

文章目录如何合适选择重试机制消费者如果保证消息幂等性,不被重复消费 如果消费者 运行时候 报错了 package com.toov5.msg.SMS; import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitLi...

Apple_Boy的博客 1406

java mq 补偿_RabbitMQ整合Spring Booot【消费者补偿幂等问题】

如果消费者 运行时候 报错了packagecom.toov5.msg.SMS;importorg.springframework.amqp.rabbit.annotation.RabbitHandler;importorg.springframework.amqp.rabbit.annotation.RabbitListener;importorg.springframework.stereoty...

weixin_35734408的博客 331

RabbitMQ整合Spring Booot【消费者补偿幂等问题】

如果消费者 运行时候 报错了 package com.toov5.msg.SMS; import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.spr...

art_code的博客 85

RabbitMQ应用问题——消息补偿机制以及幂等性的保证简单介绍

大伙可以到我的RabbitMQ专栏获取更多信息 RabbitMQ应用问题 消息可靠性的保障 ​​​​​​​消息补偿机制 消息的幂等性保障 ​​​​​​​乐观锁解决方案 ​​​​​​​消息补偿机制 这个图看起来好像很复杂,其实只做到了三件事情: 发消息 收到消息确认 检查比对是不是收到消息了 详细说明消息补偿流程 发生业务操作,业务数据写入数据库 生产者将消息发送给MQ的队列Q1 发送了一条与step2中一摸一样的延迟消息到对了Q3 消费者监听Q1,获取到了step2中

保暖透气大裤衩LeoLee的博客 2418

12 补偿机制

文章目录1 出现场景2 实现原理2.1 @RabbitListener3 自定义重试4 如何选择重试机制4.1 需要重试4.2 无需重试5 幂等性 1 出现场景 rabbitmq 默认情况下,如果消费者程序出现异常,会自动实现补偿机制 消费者出现异常,消息不会消费成功 2 实现原理 2.1 @RabbitListener @RabbitListener底层,使用Aop进行拦截,如果程序没有抛出异...

Skr郎 957

Spring-RabbitMQ-自动补偿机制

1. 自动补偿机制 在RabbitMQ里,如果消费者在处理消息时,业务逻辑出现异常,默认会执行补偿机制(也就是消息重试机制)。如果业务逻辑出现异常,是不会消费消息的。基于上一篇博客的例子《消息中间件系列教程(13) -RabbitMQ-SpringBoot集成RabbitMQ》来演示一下。 现在消费者处理消息的地方模拟一个异常: 生产者发送消息给消费者,会发现控制台一直在打印错误日志,也就是说,消费者一直在重试消费(补偿): 也可以在RabbitMQ控制台看到,消息时没有被消费的..

lzf的博客 914

什么是幂等?什么情况下需要考虑幂等?你怎么解决幂等的问题?

幂等(idempotence),来源于数学中的一个概念,例如:幂等函数/幂等方法(指用相同的参数重复执行,并能获得相同结果的函数,这些函数不影响系统状态,也不用担心重复执行会对系统造成改变)。通俗来说:就是多次调用对系统的产生的影响是一样的,即对资源的作用是一样的。幂等性,强调的是外界通过接口对系统内部的影响, 只要一次或多次调用对某一个资源应该具有同样的副作用就行。注意:这里指对资源造成的副作用必须是一样的,但是返回值允许不同!前置条件明确业务中哪些操作需要幂等性(如支付、订单创建)。分层设计。

qq_25341829的博客 2006

8 用redis解决重复消费(幂等问题)

文章目录1 生产者1.1 修改controller层1.2 修改补偿机制的代码2 消费者2.1 引入redis的依赖和配置2.2 引入redis工具类2.3用redis解决重复消费 在发送消息之后,并没有接到消息确认。他就会再次发送。则同一个消息他发送了两次,也会重复消费两次 或者 消费重试也可能造成幂等性问题 1 生产者 1.1 修改controller层 1.2 修改补偿机制的代码 pac...

Insist___的博客 4734
上一篇: 通过join方法顺序执行多个线程
下一篇: RabbitMQ整合Spring Booot【消费者补偿幂等问题】
Art&Code
博客等级 码龄7年 77粉丝 554原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值