springcloud+rabbitmq实现分布式事务【非死信队列实现】

说明:本文章是基于springcloud+springboot+rabbitmq实现的分布式事务,注册中心为eureka,服务调用为openfeign。使用简单消息队列完成分布式事务【即补偿机制】,下篇文章使用死信队列完成分布式。

业务需求:用户支付功能,如果用户支付成功,需要改变订单表中订单状态为支付状态【跨服务调用修改订单状态接口】,同时库存表中减库存数量【跨服务调用修改库存数量接口】。

下面提到的回款操作是商家不愿意看到,很多商家不愿意这样,这里回款操作只是文章需要。

实现逻辑分析:

1.如果用户支付失败,直接抛异常不走下面操作,只需要本地事务回滚即可。

2.如果用户支付成功,调用修改订单状态接口失败,需要在支付接口中抛异常,通过异常处理给已经支付从用户进行回款操作

3.如果用户支付成功,调用修改订单状态接口成功,调用修改库存接口失败【调用修改库存需要使用消息中间件处理】,也就是消息中间件发送消息失败或者消息消费失败两种情况:a.如果是发送消息失败,处理方案为采取消息发送回调机制,在回调机制中进行重试机制或者说消息发送失败根本就不想进行重试机制,那么就选择补偿机制,即在回调机制中进行回款操作和更改订单为未支付状态。b.如果是消费消息失败,那么采用手动签收消息机制,通过异常处理消息,catch到异常进行补偿机制,即回款操作和修改订单状态为未支付状态【其实回款操作也是通过消息队列实现,这里就不说了】。

不说了,上代码,代码分为  用户支付服务   和    商品订单库存服务

一:用户支付服务工程目录说明:

pom.xml文件

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

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

application.yml文件【重点为rabbitmq配置,更改你的mq配置】

server:
  port: 7200
spring:
  application:
    name: cloud-affair-start-7200-service
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/myMq?useSSL=false
    username: postgres
    password: root
  rabbitmq:
    #host: 10.88.104.177
    host: 11.11.177.170
    #host: 11.11.177.170
    port: 45672
    username: liqingwei01
    # sod-rabbitmq
    password: liqingwei01
    virtual-host: mymqdemo
    ###开启消息确认机制 confirms
    publisher-confirms: true
    publisher-returns: true
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:80/eureka


mybatis-plus:
  mapper-locations: classpath:mapping/*.xml
  type-aliases-package: com.seats.com.affstart.model
  #开启驼峰
  configuration:
    map-underscore-to-camel-case: true

#分页插件
pagehelper:
  auto-dialect: postgresql
  reasonable: true
  support-methods-arguments: true

GoodsStockModel实体

import lombok.Data;
/**
 * 商品库存
 * @author 李庆伟
 * @date 2021/6/11 10:32
 */
@Data
public class GoodsStockModel {

    private String id;//商品id

    private String goodsName;//商品名称

    private String goodsNum;//商品数量

    private String goodsType;//商品类型
}

OrderModel实体

import lombok.Data;
import java.util.Date;

/**
 * 订单
 * @author 李庆伟
 * @date 2021/6/11 10:31
 */
@Data
public class OrderModel {

    private String id;//订单id

    private String orderNo;//订单编号

    private String goodsId;//商品id

    private String goodsNum;//商品数量

    private String orderType;//订单状态  0未支付   1支付

    private Date createDate;//订单创建时间

    private String createUser;//下单人

}

RabbitMqConfig文件

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * 交换机和队列声明
 * @author 李庆伟
 * @date 2021/6/11 16:58
 */
@Component
public class RabbitMqConfig {

    // 减库存队列
    public static final String GOODS_STOCK_CUT_QUEUE = "goods_stock_cut_queue";
    // 库存队列交换机
    private static final String GOODS_STOCK_EXCHANGE = "goods_stock_exchange";

    // 1.减库存队列
    @Bean
    public Queue directGoodsCutQueue() {
        return new Queue(GOODS_STOCK_CUT_QUEUE);
    }

    // 2.库存队列交换机
    @Bean
    DirectExchange directGoodsExchange() {
        return new DirectExchange(GOODS_STOCK_EXCHANGE);
    }

    // 3.减库存队列与交换机绑定
    @Bean
    Binding bindingExchangeGoodsCutQueue() {
        return BindingBuilder.bind(directGoodsCutQueue()).to(directGoodsExchange()).with("goodsStockRoutingKey");
    }

}

RabbitGoodsStockUtil文件

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 发送消息实现减库存操作
 * @author 李庆伟
 * @date 2021/6/7 20:41
 */
@Component
public class RabbitGoodsStockUitl implements RabbitTemplate.ConfirmCallback{

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void send(String orderId) {
        // 封装消息
        Message message = MessageBuilder.withBody(orderId.getBytes()).setContentType(MessageProperties.CONTENT_TYPE_JSON)
                .setContentEncoding("utf-8").setMessageId(orderId).build();
        // 构建回调返回的数据
        CorrelationData correlationData = new CorrelationData(orderId);
        // 发送消息
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.convertAndSend("goods_stock_exchange", "goodsStockRoutingKey", message, correlationData);
        System.out.println("消息发送完成了。。。。。。。。。");
        System.out.println("消息发送完成了。。。。。。。。。");
        System.out.println("消息发送完成了。。。。。。。。。");
    }

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String orderId = correlationData.getId(); //id 都是相同的哦  全局ID
        System.out.println("消息id:" + correlationData.getId());
        if (ack) { //消息发送成功
            System.out.println("消息发送确认成功");
        } else {
            //1.如果进行重试机制
            System.out.println("消息发送确认失败:" + cause);
            //重试机制
            send(orderId);
            //2.消息发送失败根本就不想进行重试机制,就注释掉 send(orderId);
            //  2.1回款操作
            //  2.2更改订单为未支付状态(补偿机制)即调用订单支付状态接口修改订单为未支付状态
        }
    }
}

 

AffairProService文件

import com.affstart.model.OrderModel;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;


/**
 * 远程调用服务
 * @author 李庆伟
 * @date 2021/6/6 17:26
 */
@FeignClient("cloud-affair-end-9200-service")
public interface AffairProService {

    /**
     * 订单查询
     * [orderId]
     * @return {@link OrderModel}
     * @throws
     * @author 李庆伟
     * @date 2021/6/20 19:46
     */
    @PostMapping("/orders/show/{orderId}")
    public OrderModel show(@PathVariable("orderId") String orderId);

    /**
     * 修改订单状态
     * [orderModel]
     * @return {@link String}
     * @throws
     * @author 李庆伟
     * @date 2021/6/20 19:46
     */
    @PostMapping("/orders/update")
    public String update(@RequestBody OrderModel orderModel);
}

 

PaymentsController文件

import com.affstart.service.PaymentsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 支付服务
 * @author 李庆伟
 * @date 2021/6/11 10:47
 */
@RequestMapping("payments")
@RestController
public class PaymentsController {

    @Autowired
    private PaymentsService paymentsService;

    /**
     * 模拟支付接口
     *  支付完成后会调用:
     *      1.修改订单为支付状态接口
     *      2.减库存接口
     * [orderId]
     * @return {@link String}
     * @throws
     * @author 李庆伟
     * @date 2021/6/11 14:20
     */
    @PostMapping("makePayments/{orderId}")
    public String makePayments(@PathVariable("orderId") String orderId){
        paymentsService.makePayments(orderId);
        return "ok";
    }
}

PaymentsService文件

/**
 * @author 李庆伟
 * @date 2021/6/7 11:24
 */
public interface PaymentsService {

    void makePayments(String orderId);

}

PaymentsServiceImpl文件

import com.affstart.config.RabbitGoodsStockUitl;
import com.affstart.feign_service.AffairProService;
import com.affstart.model.OrderModel;
import com.affstart.service.PaymentsService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * @author 李庆伟
 * @date 2021/6/7 11:24
 */
@Service
public class PaymentsServiceImpl implements PaymentsService {

    @Resource
    private AffairProService affairProService;

    @Autowired
    private RabbitGoodsStockUitl rabbitGoodsStockUitl;

    /**
     * 模拟支付接口
     *  支付完成后会调用:
     *      1.修改订单为支付状态接口
     *      2.减库存接口
     */
    @Transactional
    public void makePayments(String orderId) {
        //调用查询订单
        OrderModel orderModel = affairProService.show(orderId);
        if(orderModel == null){
            throw new RuntimeException("订单错误。。。。");
        }
        //模拟支付
        System.out.println("支付完成啦啦啦。。。。。。。。。");

        //调用订单支付状态接口
        orderModel.setOrderType("1");
        String orderType = affairProService.update(orderModel);

        if(orderType == null || StringUtils.isEmpty(orderType)){
            throw new RuntimeException(orderId+"-订单状态修改失败" +
                    "【异常处理时需要对已经支付的用户进行回款操作,此次省略】");
        }
        //消息队列调用减库存接口
        rabbitGoodsStockUitl.send(orderId);

    }

}

 

二:订单库存服务工程目录说明:

pom.xml文件

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

 

application.yml文件【重点为rabbitmq配置,更改你的mq配置】

server:
  port: 9200
spring:
  application:
    name: cloud-affair-end-9200-service
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/myMq?useSSL=false
    username: postgres
    password: root
  rabbitmq:
    host: 11.11.177.170
    port: 45672
    username: liqingwei01
    password: liqingwei01
    virtual-host: mymqdemo
    listener:
      simple:
        retry:
          ####开启消费者(程序出现异常的情况下会)进行重试
          enabled: true
          ####最大重试次数
          max-attempts: 5
          ####重试间隔次数
          initial-interval: 3000
        ####开启手动ack
        acknowledge-mode: manual

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:80/eureka


mybatis-plus:
  mapper-locations: classpath:mapping/*.xml
  type-aliases-package: com.seats.com.affstart.model
  #开启驼峰
  configuration:
    map-underscore-to-camel-case: true

#分页插件
pagehelper:
  auto-dialect: postgresql
  reasonable: true
  support-methods-arguments: true

GoodsStockModel

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

/**
 * 商品库存
 * @author 李庆伟
 * @date 2021/6/11 10:32
 */
@Data
@TableName("t_goods_stock")
public class GoodsStockModel {

    @TableId
    private String id;//商品id

    private String goodsName;//商品名称

    private Integer goodsNum;//商品数量

    private String goodsType;//商品类型
}

实体OrderModel实体

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.util.Date;


/**
 * 订单
 * @author 李庆伟
 * @date 2021/6/11 10:31
 */
@Data
@TableName("t_order")
public class OrderModel {

    @TableId
    private String id;//订单id

    private String orderNo;//订单编号

    private String goodsId;//商品id

    private Integer goodsNum;//商品数量

    private String orderType;//订单状态  0未支付   1支付

    private Date createDate;//订单创建时间

    private String createUser;//下单人

}

 

RabbitMqConfig文件

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.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * 交换机和队列声明
 * @author 李庆伟
 * @date 2021/6/11 16:58
 */
@Component
public class RabbitMqConfig {

    // 减库存队列
    public static final String GOODS_STOCK_CUT_QUEUE = "goods_stock_cut_queue";

    // 库存队列交换机
    private static final String GOODS_STOCK_EXCHANGE = "goods_stock_exchange";

    // 1.减库存队列
    @Bean
    public Queue directGoodsCutQueue() {
        return new Queue(GOODS_STOCK_CUT_QUEUE);
    }

    // 2.库存队列交换机
    @Bean
    DirectExchange directGoodsExchange() {
        return new DirectExchange(GOODS_STOCK_EXCHANGE);
    }

    // 3.减库存队列与交换机绑定
    @Bean
    Binding bindingExchangeGoodsCutQueue() {
        return BindingBuilder.bind(directGoodsCutQueue()).to(directGoodsExchange()).with("goodsStockRoutingKey");
    }

}

GoodsStockMqListener文件

import com.affend.model.GoodsStockModel;
import com.affend.model.OrderModel;
import com.affend.service.GoodsStockService;
import com.affend.service.OrderService;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.stereotype.Component;
import com.rabbitmq.client.Channel;

import java.util.Map;

/**
 * 商品库存服务
 * @author 李庆伟
 * @date 2021/6/11 10:47
 */
@Component
public class GoodsStockMqListener {

    @Autowired
    private GoodsStockService goodsStockService;
    @Autowired
    private OrderService orderService;

    @RabbitListener(queues = "goods_stock_cut_queue")
    public void process(Message message, @Headers Map<String, Object> headers, Channel channel) throws Exception {

        String orderId = new String(message.getBody(), "UTF-8");

        OrderModel orderModel = orderService.show(orderId);

        GoodsStockModel goodsStockModel = goodsStockService.show(orderModel.getGoodsId());

        goodsStockModel.setGoodsNum(goodsStockModel.getGoodsNum() - orderModel.getGoodsNum());
        try {
            //int a = 1/0;//模拟减库存失败
            int cutNum = goodsStockService.update(goodsStockModel);//减库存操作
            if (cutNum > 0) {
                // 手动签收消息,通知mq服务器端删除该消息
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            }
        } catch (Exception e) {
            e.printStackTrace();
            orderModel.setOrderType("0");
            int orderNum = orderService.update(orderModel);//修改订单为未支付状态
            //这里省略用户回款操作
            if(orderNum == 1){
                //丢弃该消息
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
            }else {//会再次执行process方法
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            }
        }
    }
}

 

GoodsStockController文件和OrderController文件

import com.affend.model.GoodsStockModel;
import com.affend.service.GoodsStockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 商品库存服务
 * @author 李庆伟
 * @date 2021/6/11 10:47
 */
@RequestMapping("goodsStock")
@RestController
public class GoodsStockController {

    @Autowired
    private GoodsStockService goodsStockService;

    /**
     * 修改库存
     * 订单支付成后,扣除库存
     * [goodsStockModel]
     * @return {@link String}
     * @throws
     * @author 李庆伟
     * @date 2021/6/11 11:06
     */
    @PostMapping("update")
    public String update(@RequestBody GoodsStockModel goodsStockModel){
        int num = goodsStockService.update(goodsStockModel);
        if(num != 1){
            return "error";
        }
        return "ok";
    }

    /**
     * 库存详情
     * [goodsId]
     * @return {@link GoodsStockModel}
     * @throws
     * @author 李庆伟
     * @date 2021/6/20 20:10
     */
    @PostMapping("show/{goodsId}")
    public GoodsStockModel show(@PathVariable("goodsId") String goodsId){
        return goodsStockService.show(goodsId);
    }
}
import com.affend.model.OrderModel;
import com.affend.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 订单服务
 * @author 李庆伟
 * @date 2021/6/11 10:47
 */
@RequestMapping("orders")
@RestController
public class OrderController {

    @Autowired
    private OrderService orderService;

    /**
     * 修改订单
     * 支付成后后,修改订单状态
     * [orderModel]
     * @return {@link String}
     * @throws
     * @author 李庆伟
     * @date 2021/6/11 11:05
     */
    @PostMapping("update")
    public String update(@RequestBody OrderModel orderModel){
        int num = orderService.update(orderModel);
        if(num != 1){
            return "error";
        }
        return "ok";
    }

    /**
     * 根据订单id查询订单详情
     * [orderId]
     * @return {@link String}
     * @throws
     * @author 李庆伟
     * @date 2021/6/11 11:25
     */
    @PostMapping("show/{orderId}")
    public OrderModel show(@PathVariable("orderId") String orderId){
        return orderService.show(orderId);
    }
}

GoodsStockService文件和OrderService文件

import com.affend.model.GoodsStockModel;

/**
 * @author 李庆伟
 * @date 2021/6/11 10:50
 */
public interface GoodsStockService {

    int update(GoodsStockModel goodsStockModel);

    GoodsStockModel show(String goodsId);
}
import com.affend.model.OrderModel;

/**
 * @author 李庆伟
 * @date 2021/6/11 10:50
 */
public interface OrderService {

    int update(OrderModel orderModel);

    OrderModel show(String orderId);
}

GoodsStockServiceImpl文件和OrderServiceImpl文件

import com.affend.mapper.GoodsStockMapper;
import com.affend.model.GoodsStockModel;
import com.affend.service.GoodsStockService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author 李庆伟
 * @date 2021/6/11 10:50
 */
@Service
public class GoodsStockServiceImpl implements GoodsStockService {

    @Resource
    private GoodsStockMapper goodsStockMapper;

    /**
     * 修改库存
     * [goodsStockModel]
     * @return {@link int}
     * @throws
     * @author 李庆伟
     * @date 2021/6/20 20:13
     */
    public int update(GoodsStockModel goodsStockModel) {
        return goodsStockMapper.updateById(goodsStockModel);
    }

    /**
     * 库存详情
     * [goodsId]
     * @return {@link GoodsStockModel}
     * @throws
     * @author 李庆伟
     * @date 2021/6/20 20:13
     */
    public GoodsStockModel show(String goodsId) {
        return goodsStockMapper.selectById(goodsId);
    }
}
import com.affend.mapper.OrderMapper;
import com.affend.model.OrderModel;
import com.affend.service.OrderService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author 李庆伟
 * @date 2021/6/11 10:50
 */
@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderMapper orderMapper;

    /**
     * 修改订单
     * [orderModel]
     * @return {@link int}
     * @throws
     * @author 李庆伟
     * @date 2021/6/20 20:13
     */
    public int update(OrderModel orderModel) {
        return orderMapper.updateById(orderModel);
    }

    /**
     * 订单详情
     * [orderId]
     * @return {@link OrderModel}
     * @throws
     * @author 李庆伟
     * @date 2021/6/20 20:13
     */
    public OrderModel show(String orderId) {
        return orderMapper.selectById(orderId);
    }
}

GoodsStockMapper文件和OrderMapper文件

import com.affend.model.GoodsStockModel;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

/**
 * @author 李庆伟
 * @date 2021/6/11 10:53
 */
@Mapper
public interface GoodsStockMapper extends BaseMapper<GoodsStockModel> {

}
import com.affend.model.OrderModel;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

/**
 * @author 李庆伟
 * @date 2021/6/11 10:53
 */
@Mapper
public interface OrderMapper extends BaseMapper<OrderModel> {
    
}

 

库表信息:

 

到此文章结束。。。。。。

 

希望对你有所帮助。。。。。。。。。。。。。

 

下篇文章更新死信队列实现分布式事务。。。。。。。。。。。。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值