一、RPC分布式事务产生的背景
二、如何理解分布式事务最终一致性
也就是我们的线程b读取到的结果一定是为mayikt而不是yushengjun;
要么数据库A非常迅速的速度
将数据同步
给数据库B,
或者数据库A没有同步
完成之前
数据库B不能够读取
userName。
允许读取数据是为老的数据 允许读取的结果不一致性。
最终一致性
的概念:因为在分布式系统,数据的同步走网络通讯、多多少少可能会收到网络的延迟、抖动数据会产生延迟,这是非常正常现象。
所以呢,短暂的数据延迟是允许
的,但是最终结果
一定要同步
。
最终一致性 补偿和人工补偿
就是这么来的。
三、基于RabbitMQ解决分布式事务思路一
四、基于RabbitMQ解决分布式事务思路二
生产者
往我们的MQ投递消息一定要成功(生产消息确认confirm机制
) 实现重试。消费者
能够消费成功 (手动的ack机制
) 如果消费失败的情况下, MQ自动帮消费者重试生产者
第一事务先执行成功,如果执行失败
采用补单
队列。五、订单系统代码实现
public interface OrderMapper {
@Insert(value = "INSERT INTO `order_info` VALUES (#{id}, " +
"#{name}, #{orderCreatetime}, #{orderMoney}, #{orderState}, #{commodityId},#{orderId})")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
public int addOrder(OrderEntity orderEntity);
@Select("SELECT * from order_info where orderId=#{orderId};")
public OrderEntity findOrderId(@Param("orderId") String orderId);
}
@Component
public class OrderRabbitMQConfig {
/**
* 派单队列
*/
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";
/**
* 定义派单队列
*
* @return
*/
@Bean
public Queue directOrderDicQueue() {
return new Queue(ORDER_DIC_QUEUE);
}
/**
* 定义补派单队列
*
* @return
*/
@Bean
public Queue directCreateOrderQueue() {
return new Queue(ORDER_CREATE_QUEUE);
}
/**
* 定义订单交换机
*
* @return
*/
@Bean
DirectExchange directOrderExchange() {
return new DirectExchange(ORDER_EXCHANGE_NAME);
}
/**
* 派单队列与交换机绑定
*
* @return
*/
@Bean
Binding bindingExchangeOrderDicQueue() {
return BindingBuilder.bind(directOrderDicQueue()).to(directOrderExchange()).with("orderRoutingKey");
}
/**
* 补单队列与交换机绑定
*
* @return
*/
@Bean
Binding bindingExchangeCreateOrder() {
return BindingBuilder.bind(directCreateOrderQueue()).to(directOrderExchange()).with("orderRoutingKey");
}
}
@Component
@Slf4j
public class OrderProducer implements RabbitTemplate.ConfirmCallback {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 基于MQ发送投递订单消息
*/
@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);
sendMsg(msgJson);
return orderId;
}
@Async
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";
rabbitTemplate.convertAndSend(orderExchange, orderRoutingKey, msgJson, correlationData);
}
public OrderEntity createOrder(String orderId) {
OrderEntity orderEntity = new OrderEntity();
orderEntity.setName("20200728rabbit分布式事物呀");
orderEntity.setOrderCreatetime(new Date());
// 价格是300元
orderEntity.setOrderMoney(300d);
// 状态为 未支付
orderEntity.setOrderState(0);
Long commodityId = 30L;
// 商品id
orderEntity.setCommodityId(commodityId);
orderEntity.setOrderId(orderId);
return orderEntity;
}
/**
* correlationData 投递失败回调的消息
*
* @param correlationData
* @param ack true 投递到MQ成功 如果是为false情况下 消息投递失败
* @param s
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String s) {
String msg = correlationData.getId();
if (!ack) {
log.info("<<<往MQ投递消息失败>>>>:" + msg);
// 采用递归算法重试
sendMsg(msg);
return;
}
log.info("<<<往MQ投递消息成功>>>>:" + msg);
// 生产者投递多次还是is的情况下应该 人工记录
}
}
六、派单系统代码实现
@Data
public class DispatchEntity {
private Long id;
// 订单号
private String orderId;
// 派单id
private Long userId;
public DispatchEntity(String orderId, Long userId) {
this.orderId = orderId;
this.userId = userId;
}
}
@Component
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");
// 计算分配的快递员id
DispatchEntity dispatchEntity = new DispatchEntity(orderId, 1234L);
// 3.插入我们的数据库
int result = dispatchMapper.insertDistribute(dispatchEntity);
if (result > 0) {
// 手动将该消息删除
// 手动ack 删除该消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
}
public interface DispatchMapper {
/**
* 新增派单任务
*/
@Insert("INSERT into platoon values (null,#{orderId},#{userId});")
int insertDistribute(DispatchEntity distributeEntity);
}
@RestController
public class OrderController {
@Autowired
private OrderProducer orderProducer;
@RequestMapping("/sendOrder")
public String sendOrder() {
return orderProducer.sendOrder();
}
}
@SpringBootApplication
@MapperScan("com.mayikt.mapper")
//@EnableAsync
public class AppProducer {
public static void main(String[] args) {
SpringApplication.run(AppProducer.class);
}
}
修改端口号即可
11
七、分布式事务补单队列的实现
因为有事物,订单插入数据会滚,但是派单数据库已经插入数据。
需要订单惊醒补单操作
补单
消费者CreateOrderConsumer@Component
public class CreateOrderConsumer {
@Autowired
private OrderMapper orderMapper;
/**
* 订阅补单队列
*
* @param message
* @param channel
* @throws IOException
*/
@RabbitListener(queues = "order_create_queue")
public void createOrderConsumer(Message message, Channel channel) throws IOException {
// 1.获取消息
String msg = new String(message.getBody());
// 2.获取order对象
OrderEntity orderEntity = JSONObject.parseObject(msg, OrderEntity.class);
// 3.获取订单号码
String orderId = orderEntity.getOrderId();
// 4.根据订单号码查询是否存在
OrderEntity dbOrderEntity = orderMapper.findOrderId(orderId);
if (dbOrderEntity != null) {
// 手动ack 删除该消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
return;
}
// 说明该订单不存在,创建该订单
// 2.添加到我们的数据库中
int result = orderMapper.addOrder(orderEntity);
if (result > 0) {
// 手动ack 删除该消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
/***
* 补单的消费不应该和 订单生产者放到一个服务器节点
* 补单消费者如果不存在的情况下 队列缓存补单消息
* 补偿分布式事务解决框架 思想最终一致性
*/
}