備忘錄五:Spring Boot + RabbitMQ 分散式事務

百聯達發表於2019-08-16

一:分散式事務解決方案

1.兩階段提交(2PC)

第一階段:事務協調器要求每個涉及到事務的資料庫預提交(precommit)此操作,並反映是否可以提交.

第二階段:事務協調器要求每個資料庫提交資料。

案例可參照 http://blog.itpub.net/28624388/viewspace-2137095/

2.補償事務(TCC)

TCC 其實就是採用的補償機制,其核心思想是:針對每個操作,都要註冊一個與其對應的確認和補償(撤銷)操作。它分為三個階段:

Try 階段主要是對業務系統做檢測及資源預留

Confirm 階段主要是對業務系統做確認提交,Try階段執行成功並開始執行 Confirm階段時,預設 Confirm階段是不會出錯的。即:只要Try成功,Confirm一定成功。

Cancel 階段主要是在業務執行錯誤,需要回滾的狀態下執行的業務取消,預留資源釋放。

3.本地訊息表(非同步確保)

本地訊息表這種實現方式應該是業界使用最多的,其核心思想是將分散式事務拆分成本地事務進行處理。

基本思路:

a.訊息生產方,需要額外建一個訊息表,並記錄訊息傳送狀態。訊息表和業務資料要在一個事務裡提交,也就是說他們要在一個資料庫裡面。然後訊息會經過MQ傳送到訊息的消費方。如果訊息傳送失敗,會進行重試傳送。

b. 訊息消費方,需要處理這個訊息,並完成自己的業務邏輯。此時如果本地事務處理成功,表明已經處理成功了,如果處理失敗,那麼就會重試執行。如果是業務上面的失敗,可以給生產方傳送一個業務補償訊息,通知生產方進行回滾等操作。

c.生產方和消費方定時掃描本地訊息表,把還沒處理完成的訊息或者失敗的訊息再傳送一遍。如果有靠譜的自動對賬補賬邏輯,這種方案還是非常實用的。

二:Spring Boot + RabbitMQ分散式事務實現

1.pom.xml依賴配置


<dependency>

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

  2.application.yaml  rabbitmq配置


# RabbitMQ        

 rabbitmq:
   host: 112.74.105.178
   port: 5672
   username: admin
   password: admin
   virtual-host: /
   publisher-confirms: true
   publisher-returns: true
   listener:
     simple:
       acknowledge-mode: manual

3.RabbitMQConfig.java


@Configuration

public class RabbitMQConfig {
// 下單並且派單存佇列
public static final String ORDER_DIC_QUEUE = "order_dis_queue";
// 補單佇列,判斷訂單是否已經被建立
public static final String ORDER_CREATE_QUEUE = "order_create_queue";
// 下單並且派單交換機
private static final String ORDER_EXCHANGE_NAME = "order_exchange_name";

@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(new Jackson2JsonMessageConverter());
return template;
}

@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(new Jackson2JsonMessageConverter());
return factory;
}
@Bean
public Queue OrderDicQueue() {
return new Queue(ORDER_DIC_QUEUE);
}

@Bean
public Queue OrderCreateQueue() {
return new Queue(ORDER_CREATE_QUEUE);
}

@Bean
DirectExchange directOrderExchange() {
return new DirectExchange(ORDER_EXCHANGE_NAME);
}

@Bean
Binding bindingExchangeOrderDicQueue() {
return BindingBuilder.bind(OrderDicQueue()).to(directOrderExchange()).with("orderRoutingKey");
}

@Bean
Binding bindingExchangeOrderCreateQueue() {
return BindingBuilder.bind(OrderCreateQueue()).to(directOrderExchange()).with("orderRoutingKey");
}

}

4. 訊息生產者


public class MsgPushInfoServiceImpl extends ServiceImpl<MsgPushInfoMapper, MsgPushInfoEntity>

implements MsgPushInfoService, RabbitTemplate.ConfirmCallback {

@Autowired
private RabbitTemplate rabbitTemplate;

public void orderAndDsipatch() {
try {
String orderId = "123456";
JSONObject jsonObect = new JSONObject();
jsonObect.put("orderId", orderId);
String msg = jsonObect.toString();
System.out.println("msg:" + msg);

MessageProperties messageProperties = new MessageProperties();
       messageProperties.setContentType("application/json");
       messageProperties.setMessageId(orderId);
       Message message = new Message(msg.getBytes(),messageProperties);
       

CorrelationData correlationData = new CorrelationData(orderId);
rabbitTemplate.setMandatory(true);
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.convertAndSend("order_exchange_name", "orderRoutingKey", message, correlationData);

} catch (Exception e) {
e.printStackTrace();
}

}

@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
String orderId = correlationData.getId();
System.out.println("訊息id:" + orderId);
if (ack) { // 訊息傳送成功
System.out.println("訊息傳送確認成功");
} else {
// 重試機制
System.out.println("訊息傳送確認失敗:" + cause);
}
}

}

5.訊息消費者


@Component

public class DispatchReceiver {

@RabbitHandler
@RabbitListener(queues = "order_dis_queue", containerFactory = "rabbitListenerContainerFactory")
public void process(Message message, Channel channel) {
System.out.println("rev : " + message.getMessageProperties().getMessageId());
try {
System.out.println("======basicNack====="+message.getMessageProperties().getDeliveryTag());
//業務處理成功,則刪除訊息
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
//業務處理失敗,則傳送補償訊息
} catch (Exception e) {
e.printStackTrace();
}
}
}


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/28624388/viewspace-2653860/,如需轉載,請註明出處,否則將追究法律責任。

相關文章