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

RabbitMQ 可靠性、重复消费,java底层原理 public class RabbitMQConfig { @Autowired private RabbitTemplate rabbitTemplate; @PostConstruct public void enableConfirmCallback() { //confirm 监听,当消息成功发到交换机 ack = true,没有发送到交换机 ack = false //correlationData 可在发送时指定消息唯一 id rabbitTemplate.setConfirmCallback( 阅读详情

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

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("执行结束....");
    }
2027年新疆职业院校技能大赛“网络系统管理”A模块网络构建真题 2027年新疆职业院校技能大赛“网络系统管理”A模块——网络构建真题,仅供学习使用。 阅读详情

相关推荐

c语言播放背景音乐

相信很多童鞋都在为大学课设而伤透头脑,而背景音乐的使用无疑会成为其中的一个亮点(总比没有好吧(╯▽╰)),故接下来我将介绍一下如何用C播放音乐。我用的编译器是CodeBlocks16.01接下来新建一个C文件建立完后是这样滴然后添加其中mciSendString()函数是由&lt;mmsystem.h&gt;库提供的供播放音乐(MP3)的函数第一个参数是歌曲所在的绝对路径 打开音乐所在位置 复制这...

qq_41443987的博客 3万+

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

DPABINet保姆级教程:零代码完成脑网络分析与图论计算

本文提供DPABINet的零代码脑网络分析全流程指南。该工具通过图形化界面集成时间序列提取、连接矩阵构建、图论指标计算及统计分析,大幅降低技术门槛。重点解析了阈值选择、二值化与小世界属性计算等关键步骤,帮助研究者快速掌握脑网络分析与图论计算,提升科研效率与可重复性。

j2k3l4的博客 668

四路 CAN 转 4G 网关

SG‑CAN‑4G‑410 为**四路隔离 CAN 转 4G 工业网关**,把 CAN2.0A/B 报文封装成 TCP/UDP 报文,通过 4G 无线网络实现 CAN 总线远程监控、远程控制。四路 CAN 相互独立;以太网仅用于本地配置,不做业务数据传输;支持移动 / 联通 / 电信 4G 网络。

S2003120416的博客 166

RV1126B SDK移植实战:从DDR配置到USB烧写全解析

本文详细解析了RV1126B SDK移植的核心实战流程,重点攻克了DDR初始化失败与USB烧写两大难题。文章从理解启动流程入手,提供了DDR配置错误的排查步骤、SDK编译配置要点,并深入讲解了USB烧写的完整操作与常见问题解决方案,旨在帮助开发者高效完成系统移植与稳定运行。

weixin_29164497的博客 542

【GameFramework框架内置模块】8、文件系统(File System)

最近好忙,鸽了好久。不能颓废了,继续发力。今天分享的是GF框架的文件系统。首先,引用比较官方的说法:GF的FileSystem虚拟文件系统,使用类似磁盘存储的感念对零散的文件进行集中管理,优化资源加载时产生的内存分配,也可以对资源进行局部片段加载,极大的提升了资源加载的性能。通俗点讲就是,我这个模块就是加载磁盘文件的,优化加载时产生的内存分配。那么,它是怎么优化的呢?

优快云博客专家,Unity3D高级软件工程师,《Unity 3D从入门到实战》作者。 2452

汽车电子标定与测量的核心基石XCP协议

控制逻辑(算法)与标定参数(数据)。如果每次优化参数都要修改源码、重新编译并刷写Flash,整个研发周期的效率将趋近于零。标定(Calibration)技术正是为了解决这一痛点而生。它允许工程师在ECU运行过程中,动态观察内部变量(测量,Measurement),并在不中断运行的前提下实时修改控制变量(标定,Calibration)。在这一领域,XCP(Universal Measurement and Calibration Protocol,通用测量与标定协议)是公认的行业标准与核心基石。

diansandaosilaodanan的博客 380

图像算法中的 Blob 分析

在计算机视觉中,Blob 可以理解为在图像中具有相似性质(如颜色、亮度等)的像素群体。Blob 通常用作基本单元,在图像分析和处理任务中能够提取出有意义的特征。

m0_44975814的博客 2483

【计算机网络】断点续传是怎么做到的:从 Range 请求到分片上传的完整机制

断点续传在下载侧靠的是 HTTP 现成的 Range 协议,Content-Range 保证偏移对得上,If-Range 防止文件变更导致的错误拼接;上传侧没有标准,需要自己设计 init/part/complete 三段式交互,核心是让服务端记住“哪些块已经收下”,从而把失败粒度从整个文件降到单个分片。 这套机制可以迁移到任何长耗时操作的设计上。判断方法很简单:先问失败粒度是多少。如果一次操作失败就得从头再来,那它迟早会在生产环境里出问题——传输、批量导入、数据迁移、长事务,都是同一个道理。把大操作切成

2503_94545876的博客 540

【QNX+Android虚拟化方案】00 - 系列文章链接汇总

【QNX+Android虚拟化方案】00 - 系列文章链接汇总

|~~~热爱生活、努力学习的小伙汁~~~| 1万+

搞懂 ABAP 里的 Language Settings:登录语言、文本环境语言与 sy-langu 的真实含义

SAP系统中语言设置并非单一字段决定,而是由登录语言(logon language)和文本环境语言(text environment language)共同控制。登录语言在会话期间固定,而文本环境语言可通过SET LOCALE语句修改,影响文本处理、排序、异常显示等行为。开发中需区分两者:使用CL_ABAP_SYST方法获取登录语言,通过sy-langu或GET LOCALE获取当前文本环境语言。语言环境切换应谨慎处理并尽快恢复,避免影响会话中其他程序。在多语言场景下,正确处理语言设置对确保文本相关功能正常

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1307

Linux 服务器性能排查手册(2026):CPU、内存、磁盘IO、网络四大瓶颈定位

iptables 四表五链模型、firewalld zone 信任级别、云安全组与主机防火墙的关系与优先级,避坑改了防火墙把自己挡在服务器外面的翻车事故。

2502_91473995的博客 322

华为OD机试 - 荒岛求生 - 栈Stack(Python/JS/C/C++ 新系统 100分)

每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天优快云在线答疑。

学Java,找哪吒 2838

【Linux网络】TCP 服务器并发处理多客户端:从 fork 分发到双重 fork 彻底消灭僵尸进程

拆解 TCP 服务器并发演进链:fork 分发连接、SIGCHLD 循环回收僵尸、双重 fork 交孙进程给 init 收养,并给出模型选型与线上排错对照。

2301_78967866的博客 429

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

AI 芯片片上网络 NoC 深度解析:flit、虚通道与拥塞控制如何决定 GPU/TPU/LPU 的算力利用率

AI芯片算力瓶颈不在FLOPS,而在片上网络(NoC)——数据搬运的能耗与延迟决定MFU上限。物理限制下,长线延迟不随工艺缩小,迫使NoC将全局布线拆为可流水的短链路;能量账显示,一跳传输耗能可达数十次乘加操作。拓扑选择直接影响性能:二维mesh虽布线可行,但均匀流量下每节点可用带宽按1/√N衰减,实测仅达标称值30%。虚通道、双网设计、静态调度(如Groq)等技术旨在突破此瓶颈。从总线到crossbar再到多跳NoC,本质是用延迟换可行性。未来趋势:专用通路(脉动阵列)、晶圆级集成(Cerebras)、跨

我的博客 88

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

台湾行情数据API接入教程

想用程序获取台股行情,很多人第一步会搜“台股资料 API 推荐”,结果发现选择不少:证交所有免费接口,Yahoo Finance 也能查,券商还有自己的 API。真正写代码时才发现,有的数据只到收盘,有的延迟 20 分钟,有的必须先开户。本文先梳理台湾行情数据的几种获取渠道,再用 Python 从零搭一个可以运行的台股数据脚本:查代码清单、取实时报价和盘口、拉历史K线、做复权、用 WebSocket 接收实时推送,最后结合台积电 ADR 和新台币汇率计算跨市场溢价。

qq_34102871的博客 302

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

Qt网络编程实战指南:从底层Socket到现代异步架构的选型与避坑

本文梳理Qt网络编程的核心模块与演进路线,对比QTcpSocket与QNetworkAccessManager的使用边界,结合HTTP请求与TCP长连接的完整示例,说明信号槽、内存管理与事件循环的配合方式。重点记录连接池设计、证书校验、线程亲和性等实际开发中的关键决策点,帮助读者在C/S或跨平台项目中建立稳定的网络层基座。

2402_83344867的博客 294

从母钟到子钟:NTP 网络时钟系统架构与软件接口

本文从工程角度简要介绍公共场所的时间同步需求、NTP协议的定位,以及典型LED同步时钟的系统架构与硬件设计,以讯鹏科技4寸与5寸同步时钟为例。

SPZHCS的博客 159

DPVS 报文黑匣子与 Crash 自动拉黑:给转发面装上“飞行记录仪“

谁打挂了我(crash dump)、他之前发了什么(慢路径环)、他还会再来(入口拉黑)。设计上我们始终坚持两条原则:记录与决策分离(黑匣子永远只说不做),以及误伤成本优先于漏放成本(三道闸门全部通过才拉黑,expire 到期自动解封)。性能上,全量快路径摘要 + 入口查表的综合开销低于 1.3%,在 FIP 网关的流量水位下不可感知。飞行记录仪的价值不在于飞机正常飞行的时候,而在于它让每一次事故都变成一次改进。对转发面来说,同样如此。

zyun360的博客 270

HTTP、SSE、WebSocket、Socket.IO:实时通信方案深度对比与选型指南

HTTP 是基础,是整个 Web 的底座;SSE 是单向推送的轻量最优解;WebSocket 是高性能双向实时通信的底层标准;Socket.IO 是一套降低开发门槛、抹平环境差异的上层框架。技术选型永远不是“哪个技术更酷”,而是匹配业务的实时性、交互方向、网络环境、团队维护成本。简单需求优先轻方案,复杂双向实时再上 WebSocket;如果追求快速落地、兼容各种奇葩网络,Socket.IO 是很好的选择。

changshuaihua001的博客 347

计算机毕业论文系统测试怎么写:从测试用例设计到对比实验,让“工作量“看得见

本文系统阐述计算机毕业论文中“系统测试”章节的撰写方法,涵盖功能测试用例设计(等价类、边界值、判定表)、性能测试压测方案与关键指标(平均响应时间、P99、吞吐量)、对比实验设计原则及评价指标选取。强调通过量化数据、阶梯加压曲线、瓶颈定位与多维度对比,真实展现研究工作量与技术深度,提供可直接套用的测试用例表与实验方案模板,助力论文脱颖而出。

wuwangzhiduoshao的博客 301

Linux网络编程:TCP多线程远程命令执行与线程池改造

前面已经实现了 TCP Echo Server,并且通过多进程和多线程解决了多个客户端连接的问题。不过目前服务器的业务还是比较简单,客户端发送什么,服务器就返回什么。这次在原来的多线程服务器上继续做一些改动,实现一个简单的远程命令执行功能。这次主要学习了两个内容。第一个是多线程远程命令执行。通过 Command 类封装命令的接收、检查、执行和结果返回,并使用 set 对允许执行的命令进行限制。第二个是线程池版本的 TCP Echo Server。通过和std::bind。

2501_93388101的博客 370

亚远景-欧盟 NIS2 与 CRA 双法规生效,车载出口企业网络安全与过程合规建设要点

亚远景结合法规要求与车载行业特性,梳理双法规对整车与零部件出口企业的核心影响,说明如何以 ISO/SAE 21434 为核心、以 ASPICE 为过程底座,搭建满足欧盟要求的网络安全与过程合规体系。

yayuanjingkeji的博客 192

23 网络性能与 QoS:带宽、时延、吞吐计算与保障机制

带宽是上限、不是实际速度;时延由传输、传播、处理、排队四段组成。一道题让你算端到端时延和吞吐,关键是先找到瓶颈在哪一跳。QoS 靠分类、标记、队列、调度四步保障,配一张瓶颈定位+容量规划图,再跟 3 道真题。

weixin_46316564的博客 221

python如何编通讯程序

通讯程序需要处理IP地址和端口。IP地址用于标识网络上的设备,端口用于标识设备上的服务。编写通讯程序时,需要选择合适的端口号并确保它不与其他服务冲突。此外

2601_96798011的博客 178

五个压测工具简介

本文对比五款主流性能测试工具:JMeter(开源全能,适合接口与复杂业务压测)、sysbench(数据库基准调优标杆,轻量高效)、ab(极简HTTP压测,快速摸底)、LoadRunner(商业企业级,支持复杂场景与特殊协议)、mysqlslap(MySQL内置,快速验证并发能力)。各具优势,适用于不同场景,开发者可根据需求选择。

呆萌很的博客 185
上一篇: 自动补偿机制(消费者)及幂等问题
下一篇: RabbitMQ死信队列
Art&Code
博客等级 码龄7年 77粉丝 554原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值