分布式事务解决方案---消息队列

                                       通过MQ实现分布式事务

我们以简单的外卖系统逻辑举例

一、引入MQ之前的分布式架构

这个时候就会发生分布式事务问题,订单系统和配送系统的相对独立的,假设订单系统调用配送系统后超时了,然后订单系统异常回滚了,但是配送系统还是正常处理了,这就导致了事务问题,数据不一致了。

具体我们看代码示例:

订单系统:

/** 创建订单 */
@Transactional(rollbackFor = Exception.class) // 订单创建整个方法添加事务
public void createOrder(JSONObject orderInfo) throws Exception {
	// 1. 订单信息 - 插入订单系统,订单数据库(事务-1)
	orderDatabaseService.saveOrder(orderInfo);
		
	// 2. 通过http接口发送订单信息到 运单系统
	String result = callDispatchHttpApi(orderInfo);
	if (!"ok".equals(result)) {
		throw new Exception("订单创建失败,原因[运单接口调用失败]");
	}
}

/**
 * 通过http接口发送 运单系统,将订单号传过去
 * 
 * @return 接口调用结果
*/
public String callDispatchHttpApi(JSONObject orderInfo) {
	SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
	// 链接超时时间 > 3秒
	requestFactory.setConnectTimeout(3000);
	// 处理超时时间 > 2 秒
	requestFactory.setReadTimeout(2000);

	RestTemplate restTemplate = new RestTemplate(requestFactory);
	String httpUrl = "http://127.0.0.1:8080/dispatch-api/dispatch?orderId=" + orderInfo.getString("orderId");
	String result = restTemplate.getForObject(httpUrl, String.class);

	return result;
}

配送系统:

@Transactional
public void dispatch(String orderId) throws Exception {
	// 往数据库插入一条记录 调度系统数据库事务2
	String sql = "insert into table_dispatch (dispatch_seq, order_id,dispatch_content) values (UUID(), ?, ?)";
	int update = jdbcTemplate.update(sql, orderId, "派送此订单");
	if (update != 1) {
		throw new SQLException("调度数据插入失败,原因[数据库操作]");
	}
}

单个系统中都做了事务处理,但是由于是分布式系统,两边相对独立,需要有一个机制来进行分布式事务的处理,也就是要么两边都失败回滚,要么两边都成功。

二、引入MQ后的分布式架构

订单系统:

增加MQ

@Service
@Transactional(rollbackFor = Exception.class)
public class MQService {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @PostConstruct
    private void setup(){
        //消息发送成功后调用
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String s) {
                //ack为false,说明消息没有准确收到,返回
                if (!b){
                    return;
                }
                String sql = "update tb_distributed_message set msg_status=1 where unique_id=?";
                int count = jdbcTemplate.update(sql,correlationData.getId());
                if (count != 1) {
                    System.out.println("更新消息状态出错");
                }
            }
        });
    }

    /**
     * 发送MQ消息,修改本地消息表的状态
     *
     * @throws Exception
     */
    public void sendMsg(JSONObject msg) {
        rabbitTemplate.convertAndSend("createOrderExchange", "", msg.toString(),
				new CorrelationData(msg.getString("orderId")));
    }
}

然后在下单后调用MQ

/** 创建订单 */
@Transactional(rollbackFor = Exception.class) // 订单创建整个方法添加事务
public void createOrder(JSONObject orderInfo) throws Exception {
	// 1. 订单信息 - 插入订单系统,订单数据库(事务-1)
	orderDatabaseService.saveOrder(orderInfo);
	// 2. 将订单信息发布出去,等待配送系统订阅
	mQService.sendMsg(orderInfo);
}

配送系统:

对MQ进行监听,有消息就消费,将配送单的创建放到接收到MQ后操作,成功消费就给订单系统正常反馈,反之就异常,保证两边的数据统一,从而达到分布式事务的目的,当然了只要保证数据最终一致即可,不是即时的。

@Component
public class OrderDispatchConsumer {
	private final Logger logger = LoggerFactory.getLogger(OrderDispatchConsumer.class);

	@Autowired
	DispatchService dispatchService;

	@RabbitListener(queues = "orderDispatchQueue")
	public void messageConsumer(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag)
			throws Exception {
		try {
			// mq里面的数据转为json对象
			JSONObject orderInfo = JSONObject.parseObject(message);
			logger.warn("收到MQ里面的消息:" + orderInfo.toJSONString());
			Thread.sleep(5000L);

			// 执行业务操作,同一个数据不能处理两次,根据业务情况去重,保证幂等性。 (拓展:redis记录处理情况)
			String orderId = orderInfo.getString("orderId");
			// 这里就是一个分配外卖小哥...
			dispatchService.dispatch(orderId);
			// ack - 告诉MQ,我已经收到啦
			channel.basicAck(tag, false);
		} catch (Exception e) {
			// 异常情况 :根据需要去: 重发/ 丢弃
			// 重发一定次数后, 丢弃, 日志告警
			channel.basicNack(tag, false, false);
			// 系统 关键数据,永远是有人工干预
		}
		// 如果不给回复,就等这个consumer断开链接后,mq-server会继续推送

	}
}

三、测试

启动配送系统:

启动订单系统:

模拟请求(成功):

订单表和配送表正常录入

 

模拟请求(失败):

模拟请求异常,看下数据库的插入情况

都回滚了,测试完成

PS:这里的回滚需要人为处理,不能用自带的回滚机制。

 

我们在开发调试以及产品部署的过程中,难免要使用到shell工具

最近发现了一款同类产品FinalShell,还是一款良心国货。初步体验了一下,确实是良心之作。且免费(通用版),支持国货。

官网:http://www.hostbuf.com/

FinalShell是一体化的的服务器,网络管理软件,不仅是ssh客户端,还是功能强大的开发,运维工具,充分满足开发,运维需求。
特色功能:
免费海外服务器远程桌面加速,ssh加速,双边tcp加速,内网穿透。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值