demo地址:鏈接:https://pan.baidu.com/s/1kGaSCHlfhm6UvbPcRUYp4g
提取碼:11hs
分佈式事務產生的背景?
1.RPC通訊中每個服務都有自己獨立的數據源,每個數據源都互不影響.
2.在單個項目中存在多個不同jdbc連接(多數據源)
如何基於我們的MQ解決我們的分佈式事務的問題(最終一致性)
1.確保我們的生產者往我們的MQ投遞消息一定要成功.(生產者消息確認機制confirm),實現重試.
2.確保我們的消費者能夠消費成功(手動ack機制),如果消費失敗情況下,MQ自動幫消費者重試.
3.確保我們的生產者第一事務先執行成功,如果執行失敗採用補單隊列.
生產者
配置交換機 隊列 綁定 和創建
package com.zhang.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class OrderRabbitMQConfig {
@Autowired
RabbitAdmin rabbitAdmin;
/**
* 派單隊列
*/
public static final String ORDER_DIC_QUEUE = "order_dic_queue";
/**
* 補單對接
*/
public static final String ORDER_CREATE_QUEUE = "order_create_queue";
/**
* 訂單交換機
*/
private static final String ORDER_EXCHANGE_NAME = "order_exchange_name";
/**
* 定義派單隊列
*/
@Bean
public Queue directOrderDicQueue() {
return new Queue(ORDER_DIC_QUEUE);
}
/**
* 定義補派單隊列
*/
@Bean
public Queue directCreateOrderQueue() {
return new Queue(ORDER_CREATE_QUEUE);
}
/**
* 定義訂單交換機
*/
@Bean
DirectExchange directOrderExchange() {
return new DirectExchange(ORDER_EXCHANGE_NAME);
}
/**
* 派單隊列與交換機綁定
*/
@Bean
Binding bindingExchangeOrderDicQueue() {
return BindingBuilder.bind(directOrderDicQueue()).to(directOrderExchange()).with("orderRoutingKey");
}
/**
* 補單隊列與交換機綁定
*/
@Bean
Binding bindingExchangeCreateOrder() {
return BindingBuilder.bind(directCreateOrderQueue()).to(directOrderExchange()).with("orderRoutingKey");
}
//創建初始化RabbitAdmin對象
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
String string = connectionFactory.toString();
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
// 只有設置爲 true,spring 纔會加載 RabbitAdmin 這個類
rabbitAdmin.setAutoStartup(true);
return rabbitAdmin;
}
//創建交換機和對列
@Bean
public void createExchangeQueue (){
rabbitAdmin.declareExchange(directOrderExchange());
rabbitAdmin.declareQueue(directOrderDicQueue());
rabbitAdmin.declareQueue(directCreateOrderQueue());
}
}
發送訂單消息
@Component
@Slf4j
public class OrderProducer implements RabbitTemplate.ConfirmCallback{
@Autowired
private OrderMapper orderMapper;
@Autowired
private RabbitTemplate rabbitTemplate;
@Transactional
public String sendOrder() {
// 1.先創建我們訂單信息
String orderId = System.currentTimeMillis() + "";
OrderEntity orderEntity = createOrder(orderId);
// 2.添加到我們的數據庫中
int result = orderMapper.addOrder(orderEntity);
if (result <= 0) {
return null;
}
// 3.訂單數據庫插入成功的情況下, 使用MQ異步發送派單信息
String msgJson = JSONObject.toJSONString(orderEntity);
System.out.println("<<<<<<<1線程名稱是>>>>>:"+Thread.currentThread().getName());
sendMsg(msgJson);
int i=1/0;
return orderId;
}
//發送消息
public void sendMsg(String msgJson) {
// 設置生產者消息確認機制
this.rabbitTemplate.setMandatory(true);
this.rabbitTemplate.setConfirmCallback(this);
CorrelationData correlationData = new CorrelationData();
correlationData.setId(msgJson);
String orderExchange = "order_exchange_name"; //訂單交換機
String orderRoutingKey = "orderRoutingKey"; //訂單路由key
rabbitTemplate.convertAndSend(orderExchange, orderRoutingKey, msgJson, correlationData);
}
//創建訂單信息
public OrderEntity createOrder(String orderId) {
OrderEntity orderEntity = new OrderEntity();
orderEntity.setName("每特教育第六期平均就業薪資破10萬");
orderEntity.setOrderCreatetime(new Date());
// 價格是300元
orderEntity.setOrderMoney(300d);
// 狀態爲 未支付
orderEntity.setOrderState(0);
Long commodityId = 30L;
// 商品id
orderEntity.setCommodityId(commodityId);
orderEntity.setOrderId(orderId);
return orderEntity;
}
/**
* 實現ConfirmCallback接口 重寫confirm方法
* @param correlationData 投遞失敗回調消息
* @param ack true 投遞到MQ成功 false投遞消息失敗
* @param cause
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
String msg = correlationData.getId();
if(!ack){
log.info("<<<往MQ投遞消息失敗>>>>: {}" , msg);
//採用遞歸重試
sendMsg(msg);
return;
}
log.info("<<<往MQ投遞消息成功>>>>: {}" , msg);
// 生產者投遞多次還是is的情況下應該 人工記錄
}
}
消費者
派單消費者
@Component
@Slf4j
public class DispatchConsumer {
@Autowired
private DispatchMapper dispatchMapper;
@RabbitListener(queues = "order_dic_queue")
public void dispatchConsumer(Message message , Channel channel) throws IOException {
// 1.獲取消息
String msg = new String(message.getBody());
// 2.轉換json
JSONObject jsonObject = JSONObject.parseObject(msg);
String orderId = jsonObject.getString("orderId");
//主動根據orderId查詢派單是否已經派單過,如果派單,不走下面插入代碼
// 計算分配的快遞員id
DispatchEntity dispatchEntity = new DispatchEntity(orderId, 1234L);
// 3.插入我們的數據庫
int result = dispatchMapper.insertDistribute(dispatchEntity);
if (result > 0) {
// 手動將該消息刪除
// 手動ack 刪除該消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
log.info("<<<消費者派單消費成功>>> 時間: {} " ,new Date());
}
}
}
補單消費者
@Component
@Slf4j
public class OrderConsumer {
@Autowired
private OrderMapper orderMapper;
/***
* 補單的消費不應該和 訂單生產者放到一個服務器節點
* 補單消費者如果不存在的情況下 隊列緩存補單消息
* 補償分佈式事務解決框架 思想最終一致性
*/
@RabbitListener(queues = "order_create_queue")
public void dispatchConsumer(Message message , Channel channel) throws IOException {
// 1.獲取消息
String msg = new String(message.getBody());
// 2.轉換json
OrderEntity orderEntity = JSONObject.parseObject(msg,OrderEntity.class);
String orderId = orderEntity.getOrderId();
OrderEntity result = orderMapper.findOrderId(orderId);
if (null!=result ) {
// 手動將該消息刪除
// 手動ack 刪除該消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
log.info("<<<消費者消費成功>>> 時間: {}",new Date());
return;
}
//補單
int i = orderMapper.addOrder(orderEntity);
if(i>0){
// 手動將該消息刪除
// 手動ack 刪除該消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
log.info("<<<消費者補單消費成功>>> 時間: {}",new Date());
}
}
}