优惠券兑换码生成需求——事务同步回调问题分析

前段时间收到一个优惠券兑换码的需求:管理后台针对一个优惠券发起批量生成兑换码,这些兑换码可以导出分发到各个合作渠道(比如:抖音、京东等),用户通过这些渠道获取到兑换码之后,再登录到我司研发的商城,使用兑换码兑换获得对应的优惠券。

整个需求大致分为两个部分:(1)批量生成兑换码;(2)使用兑换码兑换优惠券。接下来的几篇文章将针对批量生成兑换码功能实现过程中碰到的一系列问题进行分析描述,以便读者再碰到类似问题,可以快速解决。

文章系列如下:

《事务失效问题分析》

《事务同步回调问题分析》

《批量生成任务全局限制唯一》

在此之前,先简单介绍商城技术架构:商城后端服务均采用SpringCloud框架开发,数据库主备,商城所有服务共用一个数据库,数据库持久化框架为MybatisPlus,所有服务采用K8s技术进行部署和治理。


一、问题描述

在《事务失效问题》一文末尾,笔者抛出了一个问题:兑换码生成记录初始状态是【生成中】,事务顺利提交,则该记录状态更新为【成功】。若执行异常导致事务回滚,则该记录状态需要更新为【失败】,该怎么处理?

有读者可能会问,为什么执行异常一定要更新状态为失败呢?不处理不行吗?这里先说说业务逻辑:整个平台需要保证同时只能有一个兑换码生成任务执行,因为兑换码的生成和批量插入比较占用资源。想想如果一次性要生成100w个兑换码并入库。为了实现这个要求,每个任务在check环节会校验是否有状态为【生成中】的任务记录,如果没有,则会插入一条状态为【生成中】的任务记录(5秒内禁止重复提交)。如果兑换码生成异常,事务回滚,没有将【生成中】状态更新为【失败】,则平台无法再次发起兑换码生成任务。

二、问题分析与解决

事务回滚之后进行回调处理涉及的技术方案是:事务同步回调处理逻辑。关键类:TransactionSynchronizationManager,事务同步管理器,监听Spring的事务操作。事务同步管理器通常使用方式如下:

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {@Overridepublic void beforeCommit(boolean readOnly) {doSometing1();    //事务提交前处理逻辑}@Overridepublic void beforeCompletion() {doSometing2();    //事务完成前处理逻辑}@Overridepublic void afterCommit() {doSometing3();    //事务提交后处理逻辑}@Overridepublic void afterCompletion(int status) {doSometing4();    //事务完成后处理逻辑}
});

如果开发人员想在事务提交前、完成前、提交后和完成后,做一些额外的处理工作,则只需要覆盖重写上述4个方法。提交和完成的区别在于:无论commit还是rollback都是完成,TransactionSynchronization类中有三种完成状态定义:0表示事务提交,1表示事务回滚,2表示未知。

/** Completion status in case of proper commit. */
int STATUS_COMMITTED = 0;/** Completion status in case of proper rollback. */
int STATUS_ROLLED_BACK = 1;/** Completion status in case of heuristic mixed completion or system errors. */
int STATUS_UNKNOWN = 2;

现在我们回到兑换码生成功能中来,代码需要增加逻辑:如果事务回滚,则将兑换码生成记录状态更为失败。代码如下:

@Transactional(rollbackFor = Exception.class)
@Override
public void create(CodeCreateReqDTO codeCreateReqDTO) {StopWatch stopWatch = new StopWatch("兑换码生成");//生成兑换码并批量入库stopWatch.start("生成随机code");List<String> codeList = RedeemCodeUtils.generateRedeemCodes(codeCreateReqDTO.getNumber());stopWatch.stop();stopWatch.start("构建对象列表");List<DhCode> dhCodeList = codeList.stream().map(s -> {DhCode dhCode = new DhCode();...return dhCode;}).collect(Collectors.toList());stopWatch.stop();try {stopWatch.start("批量写入");if(!dhCodeService.saveBatch(dhCodeList)) throw new BusinessException(CommonConstants.FAIL, "批量保存兑换码失败!");stopWatch.stop();stopWatch.start("更新数量和状态");//更新优惠券已生成兑换码数量和未兑换的兑换码数量, 更新兑换码生成记录状态if(updateDhCodeNumberAndGenerateStatus(number, SUCCESS))) {log.info("优惠券生成兑换码成功!");} else {throw new BusinessException(CommonConstants.FAIL, "更新优惠券兑换码数量或兑换码记录状态失败!");}stopWatch.stop();} catch (Exception e) {log.warn("兑换码生成失败!", e);TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();    // 这里非常重要,否则注册回调无法生效}//注册事务同步回调TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {@Overridepublic void afterCompletion(int status) {if(TransactionSynchronization.STATUS_ROLLED_BACK == status){log.warn("========afterCompletion=========事务回滚============");updateGenerateStatus(FAIL);    //更新兑换码生成记录状态为失败}}});log.info(stopWatch.prettyPrint(TimeUnit.SECONDS));
}

上述代码中,有2个地方需要注意:

(1)第32-35行,这里必须catch所有异常,且不能再往外抛,且增加TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 只有这样子,注册事务同步回调机制才能生效;

(2)第38-46行,则是具体的事务回滚处理逻辑;

至此,文章开头抛出的问题可以顺利解决。那么细心的读者在看业务逻辑图时,可能带发现一个问题:插入兑换生成记录时如何保证并发安全?从下面的流程图来看,这里是存在并发问题,有可能会同时写入两条记录,那么就不能保证平台同时只有一个兑换码任务在执行了。请读者继续阅读《批量生成任务全局限制唯一》。

三、参考资料

  1. 兑换码生成工具类下载
  2. 《Spring进阶篇(7)-TransactionSynchronizationManager(事务监听)》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/615170.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Docker官方php:7.4.33-fpm镜像构建支持67个常见模组的php7.4.33镜像

实践说明&#xff1a;基于RHEL7(CentOS7.9)部署docker环境(23.0.1、24.0.2)&#xff0c;所构建的php7.4.33镜像应用于RHEL7-9(如AlmaLinux9.1)&#xff0c;但因为docker的特性&#xff0c;适用场景是不限于此的。 文档形成时期&#xff1a;2017-2023年 因系统或软件版本不同&am…

鸿鹄云商B2B2C:JAVA实现的商家间直播带货商城系统概览

【saas云平台】打造全行业全渠道全场景的saas产品&#xff0c;为经营场景提供一体化解决方案&#xff1b;门店经营区域化、网店经营一体化&#xff0c;本地化、全方位、一站式服务&#xff0c;为多门店提供统一运营解决方案&#xff1b;提供丰富多样的营销玩法覆盖所有经营场景…

Flink集成Hive之Hive Catalog

流程流程: Flink消费Kafka,逻辑处理后将实时流转换为表视图,利用HiveCataLog创建Hive表,将实时流 表insert进Hive,注意分区时间字段需要为 yyyy-MM-dd形式,否则抛出异常:java.time.format.DateTimeParseException: Text 20240111 could not be parsed 写入到hive分区表 strea…

在 CentOS 7上创建本地 YUM 仓库,并且提供给其它服务器做yum源

在 CentOS 7.6 上创建本地 YUM 仓库的步骤如下&#xff1a; 上传 CentOS 镜像文件&#xff1a; 确保你已经将 CentOS 7.6 的 ISO 镜像文件上传到了服务器上。例如&#xff0c;假设你已经上传到 /path/to/your/iso 路径。 挂载 ISO 镜像&#xff1a; 你需要将 ISO 镜像文件挂载…

数据库与低代码:加速开发,提升效率的完美结合

随着技术的不断进步&#xff0c;数据库和低代码开发成为了现代应用程序开发中的两大关键要素。本文将探讨如何通过结合数据库和低代码开发&#xff0c;加速应用程序的开发过程&#xff0c;并提高开发效率和质量。 在过去的几十年中&#xff0c;数据库一直被视为应用程序开发中不…

使用srs_librtmp实现RTMP推流

1、背景 由于项目有需求在一个现有的产品上增加RTMP推流的功能&#xff0c;目前只推视频流。 2、方案选择 由于是在现有的产品上新增功能&#xff0c;那么为了减少总的成本&#xff0c;故选择只动应用软件的来实现需求。 现有的产品中的第三方库比较有限&#xff0c;连个ffmp…

Linux CentOS 7.6安装nginx详细保姆级教程

一、通过wget下载nginx压缩包 1、进入home文件并创建nginx文件夹用来存放nginx压缩包 cd /home //进入home文件夹 mkdir nginx //创建nginx文件夹 cd nginx //进入nginx文件夹2、下载nginx,我这里下载的是Nginx 1.24.0版本&#xff0c;如果要下载新版本可以去官网进行下载:…

堆排序(Java语言)

视频讲解地址&#xff1a;【手把手带你写十大排序】7.堆排序&#xff08;Java语言&#xff09;_哔哩哔哩_bilibili 代码&#xff1a; public class HeapSort {public void swap(int[] array, int index1, int index2) {array[index1] array[index1] ^ array[index2];array[i…

回归预测 | Matlab基于SMA+WOA+SFO-LSSVM多输入单输出回归预测

回归预测 | Matlab基于SMAWOASFO-LSSVM多输入单输出回归预测 目录 回归预测 | Matlab基于SMAWOASFO-LSSVM多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 SMAWOASFO-LSSVM回归预测 基于黏菌算法鲸鱼算法向日葵算法优化LSSVM回归预测 其中包含三种改进…

RSIC-V“一芯”学习笔记(一)——概述

考研的文章和资料之后想写的时候再写怕趴 文章目录 一、阶段设计二、环境、开发语言和工具三、最重要的两个观念四、处理器芯片设计五、处理器芯片设计包含很多软件问题六、处理器芯片的评价指标七、复杂系统的构建和维护八、专业世界观九&#xff0c;提问的艺术(提问模板)十、…

pandas创建一个新的dataframe对象

import pandas as pd df pd.DataFrame() print(df)

ddos攻击会让服务器受到什么影响?-速盾网络(sudun)

DDoS攻击是一种网络攻击手段&#xff0c;它通过利用大量的请求或恶意流量超过服务器的处理能力&#xff0c;从而导致服务器无法正常工作或服务质量显著下降。 首先&#xff0c;DDoS攻击会对服务器的带宽造成极大的压力。攻击者会利用大量的机器或网络资源发起攻击&#xff0c;…

Ubuntu下使用Virtual Box中显示没有可用的USB设备

Ubuntu中使用Virtual Box&#xff0c;但是使用到USB时只有USB1.1可以使用&#xff0c;并且提示没有可以使用的USB设备&#xff0c;解决方法如下 下载并安装Vitrual Box提供的功能扩展包 分别点击帮助->关于&#xff0c;查看当前使用的版本进入到Virtual Box官网下载链接根…

MATLAB中untrace函数用法

目录 语法 说明 untrace函数的功能是在仿真调试会话中移除跟踪点。 语法 untrace blk 说明 untrace blk 从当前仿真调试会话的跟踪点列表中移除块 blk 的跟踪点。每当在仿真调试会话中执行块时&#xff0c;软件会显示与跟踪点对应的块的信息。 当以编程方式启动仿真调试会…

vue前端开发自学练习,Props数据传递-类型校验,默认值的设置!

vue前端开发自学练习,Props数据传递-类型校验,默认值的设置&#xff01; 实际上&#xff0c;vue开发框架的时候&#xff0c;充分考虑到了前端开发人员可能会遇到的各种各样的情况&#xff0c;比如大家经常遇到的&#xff0c;数据类型的校验&#xff0c;再比如&#xff0c;默认…

Spring之整合Mybatis底层源码

文章目录 一、整体核心思路1 . 简介2. 整合思路 二、源码分析1. 环境准备2. 源码分析 一、整体核心思路 1 . 简介 有很多框架需要与Spring进行整合&#xff0c;而整合的核心思路就是把其他框架所产生的对象放到Spring容器中&#xff0c;让其成为一个bean。比如Mybatis&#x…

在Colab上测试Mamba

我们在前面的文章介绍了研究人员推出了一种挑战Transformer的新架构Mamba 他们的研究表明&#xff0c;Mamba是一种状态空间模型(SSM)&#xff0c;在不同的模式(如语言、音频和时间序列)中表现出卓越的性能。为了说明这一点&#xff0c;研究人员使用Mamba-3B模型进行了语言建模…

Oladance、南卡、Cleer开放式耳机怎么样?全方位测评大PK!

​开放式耳机作为新兴的音频设备领域中备受欢迎的选择&#xff0c;但市场上琳琅满目的产品汇集了质量千差万别的耳机&#xff0c;其中存在着一些粗制滥造的产品。身为一位音频设备测评博主&#xff0c;我经常收到有关哪个品牌的开放式耳机质量好的疑问。面对市面上众多选择&…

MFC结合GDI+

MFC结合GDI 创建一个空的MFC界面&#xff0c;在确定按钮函数里进行画图&#xff1a; 1、包含头文件与库 在stdafx.h中加入以下三行代码&#xff1a; #include "gdiplus.h" using namespace Gdiplus; #pragma comment(lib, "gdiplus.lib")2、安装GDI 在…

常州ipad签约 电子签约 战略签约 启动签约

ipad电子签约、签约上墙、多人签约、电子签约、签约仪式、签约软件、签军令状、签字上屏、屏幕签字等&#xff0c;并且本公司ipad签约可以实现 甲方对应N个乙方签约系统对多人&#xff1a;是指只有一个甲方&#xff0c;多个第三方。甲方只要签名一次就可以自动帖加指定的位置 …