RabbitMq的重试机制以及解决幂等性问题

如何适合选择重试机制
情况一:消费者获取消息后,调用第三方接口,但接口暂时无法访问,是否需要重试呢?
答案:需要重试的 需要加以判断,并设置重试机制
情况二:消费者获取消息后,抛出数据转换异常,是否需要重试?
答案:不需要, 因为是程序出现异常,再重试多少次都没有用,需要改bug重新发布新版本才能解决问题
解决方案:应采用日志记录——定时任务 job健康检查+人工进行补偿
消费者如何保证消息幂等性,不被重复消费
产生呢个原因:网络延迟传输中,会导致MQ重试,在充实的过程中,可能会导致成重复消费

解决办法:
使用全局MessageID判断消费方使用同一个,解决幂等性。
基于全局消息Id区分消息,解决幂等性
生产者:
请求头设置消息id(message id)
代码如下
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", "644064779");
	jsonObject.put("timestamp", System.currentTimeMillis());
	String jsonString = jsonObject.toJSONString();
	System.out.println("jsonString:" + jsonString);
	// 生产者发送消息的时候需要设置消息id

	Message message = MessageBuilder.withBody(jsonString.getBytes())
			.setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8")
			.setMessageId(UUID.randomUUID() + "").build();

	amqpTemplate.convertAndSend(queueName, message);
}

}
消费者:
核心代码

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 {

// rabbitmq 默认情况下 如果消费者程序出现异常的情况下,会自动实现补偿机制
// 补偿(重试机制) 队列服务器 发送补偿请求
// 如果消费端 程序业务逻辑出现异常消息会消费成功吗?
// @RabbitListener(queues = "fanout_email_queue")
// public void process(String msg) throws Exception {
// System.out.println("邮件消费者获取生产者消息msg:" + msg);
// JSONObject jsonObject = JSONObject.parseObject(msg);
// String email = jsonObject.getString("email");
// String emailUrl = "http://127.0.0.1:8083/sendEmail?email=" + email;
// System.out.println("邮件消费者开始调用第三方邮件服务器,emailUrl:" + emailUrl);
// JSONObject result = HttpClientUtils.httpGet(emailUrl);
// // 如果调用第三方邮件接口无法访问,如何实现自动重试.
// if (result == null) {
// throw new Exception("调用第三方邮件服务器接口失败!");
// }
// System.out.println("邮件消费者结束调用第三方邮件服务器成功,result:" + result + "程序执行结束");
//
// }
// @RabbitListener 底层 使用Aop进行拦截,如果程序没有抛出异常,自动提交事务
// 如果Aop使用异常通知拦截 获取异常信息的话,自动实现补偿机制 ,该消息会缓存到rabbitmq服务器端进行存放,一直重试到不抛异常为准。

// 修改重试机制策略 一般默认情况下 间隔5秒重试一次

// MQ重试机制需要注意的问题
// MQ消费者幂等性问题如何解决:使用全局ID

@RabbitListener(queues = "fanout_email_queue")
public void process(Message message, @Headers Map<String, Object> headers, Channel channel) throws Exception {
	String messageId = message.getMessageProperties().getMessageId();
	String msg = new String(message.getBody(), "UTF-8");
	System.out.println("邮件消费者获取生产者消息msg:" + msg + ",消息id:" + messageId);
	// 重试机制都是间隔性

	JSONObject jsonObject = JSONObject.parseObject(msg);
	String email = jsonObject.getString("email");
	String emailUrl = "http://127.0.0.1:8083/sendEmail?email=" + email;
	System.out.println("邮件消费者开始调用第三方邮件服务器,emailUrl:" + emailUrl);
	JSONObject result = HttpClientUtils.httpGet(emailUrl);
	// 如果调用第三方邮件接口无法访问,如何实现自动重试.
	if (result == null) {
		throw new Exception("调用第三方邮件服务器接口失败!");
	}
	System.out.println("邮件消费者结束调用第三方邮件服务器成功,result:" + result + "程序执行结束");
	// 手动ack
	Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
	// 手动签收
	channel.basicAck(deliveryTag, false);

}
// 默认是自动应答模式

}
在配置文件中添加配置信息
spring:
rabbitmq:
####连接地址
host: 127.0.0.1
####端口号
port: 5672
####账号
username: guest
####密码
password: guest

地址

virtual-host: /admin_host
listener:
  simple:
    retry:
    ####开启消费者重试
      enabled: true
     ####最大重试次数
      max-attempts: 5
    ####重试间隔次数
      initial-interval: 3000

server:
port: 8081

RabbitMQ消费者重试调用接口
//邮件队列
@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("执行结束....");

}

}
RabbitMQ签收模式
//邮件队列
@Component
public class FanoutEamilConsumer {
@RabbitListener(queues = “fanout_email_queue”)
public void process(Message message, @Headers Map<String, Object> headers, Channel channel) throws Exception {//就是在这里添加的@Header设置
System.out
.println(Thread.currentThread().getName() + “,邮件消费者获取生产者消息msg:” + new String(message.getBody(), “UTF-8”)
+ “,messageId:” + message.getMessageProperties().getMessageId());
// 手动ack
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
// 手动签收
channel.basicAck(deliveryTag, false);
}
}
开启手动应答

spring:
rabbitmq:
####连接地址
host: 127.0.0.1
####端口号
port: 5672
####账号
username: guest
####密码
password: guest

地址

virtual-host: /admin_host
listener: 
  simple:
    retry:
    ####开启消费者异常重试
      enabled: true
     ####最大重试次数
      max-attempts: 5
    ####重试间隔次数
      initial-interval: 2000
    ####开启手动ack  
    acknowledge-mode: manual 

在这里插入图片描述
在进行第三方接口调用的时候项目中使用的HttpClient工具类
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;
      }

}
在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值