消費者出現業務邏輯問題,自動補償機制
場景演示:
/**
* @author 孫一鳴 on 2020/3/15
*/
@Service
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "sym"),
exchange = @Exchange(value = "exchange.direct",type = "direct"),
key = "sym"
))
public class rabbitService {
@RabbitListener(queues = "sym")
public void recive (Map map){
System.out.println(map.get("data"));
int i=1/0;
}
}
@RabbitListener 底層 使用Aop進行攔截,如果程序沒有拋出異常,自動提交事務
// 如果Aop使用異常通知攔截 獲取異常信息的話,自動實現補償機制 ,該消息會緩存到rabbitmq服務器端進行存放,一直重試到不拋異常爲準。
補償(重試機制) 隊列服務器 發送補償請求
修改重試機制策略 一般默認情況下 間隔3秒重試一次
發送消息者發送消息,消費者消費消息時出現異常,此時隊列重試3次
出現3次日誌顯示
調用第三方接口,如果出現異常,調用重試機制
@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);
}
// 默認是自動應答模式
MQ重試機制需要注意的問題
如何合適選擇重試機制:
- 情況1: 消費者獲取到消息後,調用第三方接口,但接口暫時無法訪問,是否需要重試? (需要重試機制)
- 情況2: 消費者獲取到消息後,拋出數據轉換異常,是否需要重試?(不需要重試機制)需要發佈進行解決。
消費者如果保證消息冪等性,不被重複消費
產生原因:網絡延遲傳輸中,消費出現異常或者是消費延遲消費,會造成MQ進行重試補償,在重試過程中,可能會造成重複消費。
解決辦法:
①使用全局MessageID判斷消費方使用同一個,解決冪等性。
@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);
}
}
②或者使用業務邏輯保證唯一(比如訂單號碼)