分布式事务第九弹——数据最终一分布式事务(三)

104 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第16天,点击查看活动详情

每日英语:

Don't judge each day by the harvest you reap but by the seeds you plant.

翻译:不要用你的收成来评价你的每一天,而要看你每天播种了多少。 ——罗伯特·史蒂文森

支付工程创建

1)Api

创建mall-pay-api并创建支付日志记录实体Bean

@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value = "pay_log")
public class PayLog {
​
    @TableId(type = IdType.ASSIGN_UUID)
    private String id;
    private Integer status;
    private String content;
    private String payId;
    private Date createTime;
}

2)Service

创建mall-pay-service,并且引入mall-pay-api依赖

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mall-service</artifactId>
        <groupId>com.xz.mall</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>mall-pay-service</artifactId>
    <description>
        支付微服务
    </description>
​
    <dependencies>
        <!--支付Api-->
        <dependency>
            <groupId>com.xz.mall</groupId>
            <artifactId>mall-pay-api</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

bootstrap.yml

server:
  port: 8090
spring:
  application:
    name: mall-pay
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.xxx.xxx:3306/shop_pay?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 123456
  cloud:
    nacos:
      config:
        file-extension: yaml
        server-addr: 192.168.xxx.xxx:8848
      discovery:
        #Nacos的注册地址
        server-addr: 192.168.xxx.xxx:8848
  main:
    allow-bean-definition-overriding: true
feign:
  client:
    config:
      default:
        connectTimeout: 10000
        readTimeout: 600000

# ====================MybatisPlus====================
mybatis-plus:
  mapper-locations: mapper/*.xml
  type-aliases-package: com.xz.mall.*.model
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#日志配置
logging:
  pattern:
    console: "%msg%n"

启动类com.xz.mall.MallPayApplication

@SpringBootApplication
@MapperScan(basePackages = "com.xz.mall.pay.mapper")
public class MallPayApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(MallPayApplication.class,args);
    }
}

事务消息发送

当接到微信支付服务器返回的支付结果后,将支付结果记录到数据库,同时将数据发送到RocketMQ中,并且以事务消息发送。

1)配置bootstrap.yml,添加RocketMQ配置

#producer
rocketmq:
  name-server: 192.168.xxx.xxx:9876
  producer:
    group: pay-group
    send-message-timeout: 300000
    compress-message-body-threshold: 4096
    max-message-size: 4194304
    retry-times-when-send-async-failed: 0
    retry-next-server: true
    retry-times-when-send-failed: 2

注意消息操作,需要引入如下包:

<!--rocketmq-->
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.0.2</version>
</dependency>

2)Dao

创建com.xz.mall.pay.mapper.PayLogMapper

public interface PayLogMapper extends BaseMapper<PayLog> {
}

3)Service

接口:创建com.xz.mall.pay.service.PayLogService并添加记录日志方法

public interface PayLogService extends IService<PayLog> {
    void log(PayLog payLog);
}

实现类:创建com.xz.mall.pay.service.impl.PayLogServiceImpl,并实现添加日志

@Service
public class PayLogServiceImpl extends ServiceImpl<PayLogMapper,PayLog> implements PayLogService {
​
    @Autowired
    private PayLogMapper payLogMapper;
​
    /***
     * 记录日志
     * @param payLog
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void log(PayLog payLog) {
        //本地操作
        int count = payLogMapper.insert(payLog);
    }
}

4)Controller

创建com.xz.mall.pay.controller.WeixinPayController,调用增加日志方法,并向RocketMQ发送事务消息

@RestController
@RequestMapping(value = "/wx")
public class WeixinPayController {
​
    @Autowired
    private PayLogService payLogService;
​
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
​
    /***
     * 记录支付结果
     * 执行事务消息发送
     */
    @GetMapping(value = "/result")
    public RespResult payLog(){
        //记录日志
        PayLog payLog = new PayLog("123",1,"test","No001",new Date());
        Message message = MessageBuilder.withPayload(JSON.toJSONString(payLog)).build();
        rocketMQTemplate.sendMessageInTransaction("rocket","log",message,null);
        return RespResult.ok();
    }
}

5)事务消息监听

创建com.xz.mall.pay.mq.TransactionListenerImpl实现事务操作

@Component
@RocketMQTransactionListener(txProducerGroup = "rocket")
public class TransactionListenerImpl implements RocketMQLocalTransactionListener {
​
    @Autowired
    private PayLogService payLogService;
​
    /***
     * 发送prepare消息成功后回调该方法用于执行本地事务
     * @param message:回传的消息,利用transactionId即可获取到该消息的唯一Id
     * @param o:调用send方法时传递的参数,当send时候若有额外的参数可以传递到send方法中,这里能获取到
     * @return
     */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
        try {
            //================本地事务操作开始=====================================
            //将o转成PayLog
            String result = new String((byte[]) message.getPayload(),"UTF-8");
            PayLog payLog = JSON.parseObject(result,PayLog.class);
            payLogService.log(payLog);
            //================本地事务操作结束=====================================
        } catch (Exception e) {
            //异常,消息回滚
            e.printStackTrace();
            return RocketMQLocalTransactionState.ROLLBACK;
        }
        return RocketMQLocalTransactionState.COMMIT;
    }
​
    /***
     * 消息回查
     * @param message
     * @return
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        return RocketMQLocalTransactionState.COMMIT;
    }
}

事务消息监听

我们只需要在mall-order-service中监听消息即可,注意消息操作,需要引入如下包:

<!--rocketmq-->
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.0.2</version>
</dependency>

1)bootstrap.yml配置

#RocketMQ消费者配置
rocketmq:
  name-server: 192.168.xxx.xxx:9876

2)消费监听

创建com.xz.mall.order.mq.OrderResultListener实现监听

@Component
@RocketMQMessageListener(topic = "log", consumerGroup = "order-group")
public class OrderResultListener implements RocketMQListener,RocketMQPushConsumerLifecycleListener {
​
    /***
     * 监听消息
     * 实现RocketMQPushConsumerLifecycleListener监听器之后,此方法不调用
     * @param message
     */
    @Override
    public void onMessage(Object message) {
    }
​
    /***
     * 消息监听
     * @param consumer
     */
    @Override
    public void prepareStart(DefaultMQPushConsumer consumer) {
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                try {
                    for (MessageExt msg : msgs) {
                        String result = new String(msg.getBody(),"UTF-8");
                        System.out.println("result:"+result);
                    }
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
                //消费状态
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
    }
}

我们发送数据测试,消费端打印结果如下:

result:{"content":"test","createTime":1665920546000,"id":"123","payId":"No001","status":1}

总结

本篇用具体代码实现了一下RocketMQ中TransactionProducer(事务),完成了数据最终一致性分布式事务。