啊?这也算事务?!

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析

阶段4、深入jdk其余源码解析

阶段5、深入jvm源码解析

事务的难点在哪?

之前分享过一个观点,设计模式最难的不是代码、也不是设计思想,而是如何准确判断每种设计模式的使用时机。一些人可能对23种设计模式如数家珍、倒背如流,却常常在阴沟里翻船,不知不觉就用了各种if else到处打补丁。

同理,事务本身其实也不难,无论本地事务还是分布式事务,业界都有成熟的解决方案。事务最大的问题也在使用时机:

人们往往不知道此处需要事务

佣金错误案例

日常开发时,我们很容易写出下面的代码:

@Transactional(rollbackFor = Exception.class)
public void execute() {updateUserPoint();updateUserLevel();
}

由于用户积分和用户等级需要满足“要么同时成功,要么同时失败”的特性,所以加上了@Transactional保证事务。很多人都十分清楚@Transactional什么情况下会失效,但事务往往总在你意想不到的地方失效。

请大家观察下面这段代码,看看有什么问题(代码做了适当简化):

// 根据订单号获取佣金
public List<OrderCmsDO> getCmsList(String outerOid) {if (StringUtil.isEmpty(outerOid)) {return Collections.emptyList();}List<OrderCmsDO> cmsList;try {// 构造请求参数CmsCalculationReqBO reqBO = generateParam(outerOid);log.info("CmsCalculateBiz.getCmsList, outerOid:{}, reqBO:{}", outerOid, reqBO);// 计算佣金cmsList = cmsCalculatorSelector.select(reqBO).calculateAllCms(reqBO);log.info("CmsCalculateBiz.getCmsList, outerOid:{}, reqBO:{}, cmsList:{}", outerOid, reqBO, cmsList);} catch (Exception e) {log.info("CmsCalculateBiz.getCmsList, request error, outerOid:{}", outerOid, e);}return cmsList;
}// 计算佣金(每计算一种类型的cms,就往cmsList中add)
public List<OrderCmsDO> calculateAllCms(CmsCalculationReqBO reqBO) {// ...CmsCalculateContextBO contextBO = generateCmsContextBO(reqBO);/*** 注意:以下执行顺序不能改变!!!!!** 计算V0 分享奖励* 计算V1 自售或者分享奖励* 计算V2 平台补贴* 计算V2 自售补贴* 计算V2 自售或者分享奖励* 计算V2 星火奖励* 计算V3 平台补贴* 计算V3 自售补贴* 计算V3 自售或者分享奖励* 计算V3 星火奖励*/calculateV0Profit(reqBO, contextBO);calculateV1Profit(reqBO, contextBO);calculateV2SubsidyProfit(reqBO, contextBO);calculateV2Profit(reqBO, contextBO);calculateV2Spark(reqBO, contextBO);calculateV3SubsidyProfit(reqBO, contextBO);calculateV3Profit(reqBO, contextBO);calculateV3Spark(reqBO, contextBO);return contextBO.getCmsList();
}

大致逻辑是:

上游方法依赖getCmsList(),然后批量插入用户下单所得佣金,具体佣金计算逻辑在calculateAllCms()。一个用户下单所得佣金比较复杂,最终返回的cmsList类似这种:

[{"bizId": "xxx","cmsAmt": 1,"cmsDesc": "V2平台补贴","outerOid": "xxx","uid": 10086},{"bizId": "xxx","cmsAmt": 15,"cmsDesc": "V2分享奖励","outerOid": "xxx","uid": 10086},{// 本订单其他类型佣金...}
]

一个用户,它的佣金假设有A、B、C三种类型,要么同时插入3种佣金,要么都不插入(后期有问题直接重跑即可),最怕的就是那种只插入一半的情况,修复很麻烦(需要删除原有的,甚至还要回滚由佣金引发的一系列操作)。

而上面的代码,就可能导致部分佣金写入的问题:

假设calculateAllCms()中需要计算3类佣金,A、B都没问题,C类佣金计算失败抛异常,那么就会进入上面的catch代码块。然而,此时cmsList中已经有A、B类佣金,代码继续往下走,就会return cmsList,返回了不完整的佣金列表,最终数据库插入的就是不完整的佣金(缺少C类佣金)。

catch里面可以直接返回emptyList,不插入总比插入不完整的好,后面再补就是了。

小结

分布式应用中也很容易因为疏忽导致数据不一致,比如我们往往会引入Manager层,为的是对其他Service的API接口做一层封装:

@Slf4j
@Component
public class MemberServiceManager {@Resourceprivate MemberDirectRecordService memberDirectRecordService;'public MemberDirectRecordTO getDirectRecord(Long uid) {if (Validator.isNotId(uid)) {return null;}try {ServiceResultTO<MemberDirectRecordTO> resultTO =memberDirectRecordService.getDirectRecord(uid);if (Validator.isNullOrEmpty(resultTO) || Validator.isFalse(resultTO.getSuccess())) {return null;}return resultTO.getData();} catch (Exception e) {log.error("getDirectRecord, error, uid:{}", uid, e);}return null;}}

上面这种写法,由于异常被吞了,调用者往往很难区分到底是远程调用超时导致null,还是接口查询本身为null。如果调用者的逻辑是:

if(result不为null){ // 本意是不为null进行一些操作do something;   
}

但如果这个“null”仅仅是因为RPC远程调用失败导致的,而不是对应的数据真的为null,本次操作就会被遗漏,效果和上面的佣金计算是一样的。

这些,其实都是事务范畴,try catch处理不当,容易导致问题被忽略,最终发生数据不一致的问题。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

GBASE南大通用-GBase 8s数据库日志模式及切换

一、 GBase 8s数据库共有以下 4 种日志模式&#xff1a;无日志模式、缓冲日志模式、无缓冲日志模式、ANSI 模式。详细介绍如下&#xff1a; 1、无日志模式&#xff08;Non logging&#xff09;&#xff1a; 采用无日志模式时&#xff0c;所有 DML 操作都不会被记录到日志中&…

关于表格太大了jupyter无法单次处理的问题

记录下自己的心路历程…耗时耗精力 我用的数据库单个表格就很大&#xff0c;一个表格有30多G&#xff0c;jupyter无法处理这么大的表格&#xff0c;会直接把电脑的进程全部结束掉&#xff0c;结束掉要是能运行成功倒也行啊&#xff0c;然鹅…给我报错说处理不了&#xff0c;罢工…

代码随想录刷题 | Day1

今日学习目标 一、基础 数组 array类 模板类vector 数组是存放在连续内存空间上的相同类型数据的集合。 数组可以方便的通过下标索引的方式获取到下标下对应的数据。 需要两点注意的是 数组下标都是从0开始的。 数组内存空间的地址是连续的 而且大家如果使用C的话&…

儿童学python语言能做什么,儿童学python哪个机构好

这篇文章主要介绍了儿童学python哪个线上机构好&#xff0c;具有一定借鉴价值&#xff0c;需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获&#xff0c;下面让小编带着大家一起了解一下。 少儿编程python 文章目录 前言 CSP-J与CSP-S少儿编程证书含金量排名&#xff0…

linux实用技巧:ubuntu18.04安装samba服务器实现局域网文件共享

Ubuntu安装配置Samba服务与Win10共享文件 Chapter1 Ubuntu18.04安装配置Samba服务与Win10共享文件一、什么是Samba二、安装Samba1、查看是否有安装samba2、安装samba 三、配置Samba服务1、创建共享目录&#xff08;以samba_workspaces为例&#xff09;2、为samba设置登录用户3、…

Python装饰器的专业解释

装饰器&#xff0c;其实是用到了闭包的原理来进行操作的。 单个装饰器&#xff1a; 以下是一个简单的例子&#xff1a; def outer(func):print("OUTER enter ...")def wrapper(*args, **kwargs):print("调用之前......")result func(*args, **kwargs)p…

亚马逊云科技 re:Invent 2023 产品体验:亚马逊云科技产品应用实践 国赛选手带你看 Elasticache Serverless

抛砖引玉 讲一下作者背景&#xff0c;曾经参加过国内世界技能大赛云计算的选拔&#xff0c;那么在竞赛中包含两类&#xff0c;一类是架构类竞赛&#xff0c;另一类就是 TroubleShooting 竞赛&#xff0c;对应的分别为亚马逊云科技 GameDay 和亚马逊云科技 Jam&#xff0c;想必…

【数学建模美赛M奖速成系列】Matplotlib绘图技巧(三)

Matplotlib绘图技巧&#xff08;三&#xff09; 写在前面7. 雷达图7.1 圆形雷达图7.2 多边形雷达图 8. 极坐标图 subplot9. 折线图 plot10. 灰度图 meshgrid11. 热力图11.1 自定义colormap 12. 箱线图 boxplot 写在前面 终于更新完Matplotlib绘图技巧的全部内容&#xff0c;有…

php 8.4 xdebug扩展编译安装方法

最新版php8.4 xdebug扩展只能通过编译方式安装, pecl是安装不了的, 编译方法如下 下载最新版xdebug git clone https://github.com/xdebug/xdebug.git 却换入xdebug目录执行编译安装xdebug cd xdebug phpize./configure --enable-xdebugmakemake install3. 配置启用xdebug 这…

JavaScript中实现页面跳转的几种常用方法

Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍在JavaScript中实现页面跳转的几种常用方法以及部分理论知识 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &#x1f349;博主收将持续更新学习记录获&#xff0c;友友们有任何问题…

Linux:jumpserver资产管理,分配 (4)

jumpserver可以去管理&#xff0c;Linux&#xff0c;Windows和Windows server&#xff0c;数据库&#xff0c;华为,思科等…网络设备 我这里准备了 三个 分别为 centos7 win10 mysql 在jumpserver上等登录的话&#xff0c;还需要他们的用户去登录&#xff0c;这里等会我就一…

1panel使用指南(一)面板安装

一、1panel简介 1Panel是杭州飞致云信息科技有限公司推出的产品 [1]&#xff0c;帮助用户实现快速建站。 [2]是一款现代化、开源的Linux服务器运维管理面板&#xff0c;于2023年3月推出&#xff0c;深度集成WordPress和Halo&#xff0c;一键完成域名绑定、SSL证书配置等操作&a…

SSH远程登陆服务器

截取自文章&#xff1a;SSH简介及两种远程登录的方法_ssh -CSDN博客 SSH的安装 SSH分为客户端 openssh-client 和服务器 openssh-server&#xff0c;可以利用以下命令确认电脑上是否安装了客户端和服务器。 dpkg -l | grep ssh 如果只是想远程登陆别的机器只需要安装客户端&…

Eureka服务注册与发现

1. Eureka简介 Eureka采用了CS的设计架构&#xff0c;Eureka Server 作为服务注册功能的服务器&#xff0c;它是服务注册中心。而系统中的其他微服务&#xff0c;使用 Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系…

【软件工程】漫谈增量过程模型:软件开发的逐步之道

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; 软件工程 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言&#xff1a; 正文 增量过程模型&#xff08;Incremental Process Model&#xff09; 主要特点和阶段&#xff1a; 优点&#xff1…

YOLOv5算法进阶改进(10)— 更换主干网络之MobileViTv3 | 轻量化Backbone

前言:Hello大家好,我是小哥谈。MobileViTv3是一种改进的模型架构,用于图像分类任务。它是在MobileViTv1和MobileViTv2的基础上进行改进的,通过引入新的模块和优化网络结构来提高性能。本节课就给大家介绍一下如何在主干网络中引入MobileViTv3网络结构,希望大家学习之后能够…

php学习05-常量

常量可以理解为值不变的量。常量值被定义后&#xff0c;在脚本的其他任何地方都不能改变。一个常量由英文字母、下划线和数字组成&#xff0c;但数字不能作为首字母出现。 在PHP中使用define()函数来定义常量&#xff0c;该函数的语法格式如下&#xff1a; define(string cons…

uni-app uni-app内置组件

锋哥原创的uni-app视频教程&#xff1a; 2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中..._哔哩哔哩_bilibili2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中...共计23条视频&#xff0c;包括&#xff1a;第1讲 uni…

MS6459字符叠加可兼容MAX7456

MS6459 是 OSD 电路,与 MCU 配合使用,可控制不同类型的显示系统。显示点阵为 1218&#xff0c;内置部分数字、英文字符、拉丁字符和中文字符。电路集成有上电复位电路和 VRAM 清零电路用于减轻 MCU 的工作量。 主要特点 输入信号&#xff1a;混合视频信号&#xff1b; 显示字符…

简单实现一个自定义loader

webpack定义的loader需要遵循单一功能原则&#xff0c;也就是一个loader只实现一个功能。在实现开发中&#xff0c;我们会直接使用诸如蓝湖等生成的样式&#xff0c;比如 button{background: rgb(255, 85, 46); }但为了考虑主题换肤&#xff0c;我们实现的想要的可能是 butto…