啊?这也算事务?!

作者简介:大家好,我是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 {
    @Resource
    private 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处理不当,容易导致问题被忽略,最终发生数据不一致的问题。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

学Python的正确顺序千万别弄反了,到时候后悔就来不及了

学Python的正确顺序&#xff1a;从基础到高级&#xff0c;步步为营 在当今数字化时代&#xff0c;Python已成为最受欢迎的编程语言之一。它不仅广泛应用于数据分析、人工智能和Web开发等领域&#xff0c;还为初学者提供了一个友好且功能强大的平台。然而&#xff0c;学习Python…

2023 全球 AI 大事件盘点

本文来自微信公众号硅星人

python观察图像的幅度谱和相位谱——冈萨雷斯数字图像处理

原理 在图像处理中&#xff0c;当我们对图像进行傅里叶变换&#xff0c;可以得到两个重要的成分&#xff1a;幅度谱和相位谱。这些成分在图像分析和处理中扮演着关键的角色。 傅里叶变换 傅里叶变换是一种将信号从时域&#xff08;或空间域&#xff09;转换到频率域的工具。对…

EOS运行启动 keosd 和 nodeos

EOS运行启动 keosd 和 nodeos 启动keosd:启动nodeos 安装完eosio后keosd和nodeos是一起带着的&#xff0c;我把EOS的运行keosd 和 nodeos官网教程放上来&#xff1a; https://developers.eos.io/welcome/latest/getting-started-guide/local-development-environment/start-nod…

智能制造 - 你需要了解的 10 个基本要素

智能制造 - 您需要了解的 10 个基本要素 在第四次工业革命 (IoT4) 的动态格局中&#xff0c;智能制造成为生产方式的关键变革。先进技术和创新工艺的融合正在塑造工业领域的新时代。探索智能制造的基本要素以及它们如何共同重新定义制造领域的效率、生产力和敏捷性。 一、数字…

Vue+elementUI引入MessageUI展示问题

VueelementUI引入MessageUI展示问题 1.出现问题的界面 2.解决问题 import "element-plus/theme-chalk/el-message.css";

公共用例库计划--个人版(一)

1、公共用例库计划 1.1、目标 在公司测试管理体系的演变过程中&#xff0c;从禅道过渡到devops再到云效平台&#xff0c;我们已经实现了对bug和用例的有效集中管理。然而&#xff0c;在实际操作中发现&#xff0c;尽管用例管理得到了初步整合&#xff0c;但在面对不同系统和测…

详解信道容量,信道速率,安全速率的区别

目录 一. 信道容量与信道速率 二. 小结 三. 安全速率与物理层安全 3.1 香农物理层安全模型 3.2 安全信道速率 四. 补充安全中断概率&#xff08;Secrecy Outage Probability, SOP&#xff09; 五. 补充安全分集度&#xff08;Secrecy Diversity Order, SDO&#xff09; …

【深度学习】Normalizing flow原理推导+Pytorch实现

1、前言 N o r m a l i z i n g f l o w \boxed{Normalizing \hspace{0.1cm} flow} Normalizingflow​&#xff0c;流模型&#xff0c;一种能够与目前流行的生成模型—— G A N 、 V A E \boxed{\mathbf{GAN、VAE}} GAN、VAE​相媲美的模型。其也是一个生成模型&#xff0c;可是…

【网络安全 | Misc】Ditf 安恒九月赛

正文 binwalk发现rar&#xff1a; 修改后缀&#xff0c;打开需要密码&#xff1a; 修改高度&#xff1a; 得到密码&#xff1a; StRe1izia得到一个流量包&#xff1a; 联系上文搜素png得到&#xff1a; 追踪流&#xff1a; 得到Base64代码&#xff1a; flag{Oz_4nd_Hir0_lov3_F…

算法通关村第十四关—堆能高效解决的经典问题(白银)

堆能高效解决的经典问题 一、在数组中找第K大的元素 LeetCode215 给定整数数组ums和整数k,请返回数组中第k个最大的元素。请注意&#xff0c;你需要找的是数组排序后的第k个最大的元素&#xff0c;而不是第k个不同的元素。 示例1&#xff1a; 输入&#xff1a;[3,2,1,5,6,4]和…

循环生成对抗网络(CycleGAN)

一、说明 循环生成对抗网络&#xff08;CycleGAN&#xff09;是一种训练深度卷积神经网络以执行图像到图像翻译任务的方法。网络使用不成对的数据集学习输入和输出图像之间的映射。 二、基本介绍 CycleGAN 是图像到图像的翻译模型&#xff0c;就像Pix2Pix一样。Pix2Pix模型面临…

计算机组成原理复习7

内存管理 文章目录 内存管理存储器概述存储器的分类按在计算机中的作用&#xff08;层次&#xff09;分类按存储介质分类按存取方式分类按信息的可保存性分类 存储器的性能指标存储容量单位成本存储速度&#xff1a;数据传输率数据的宽度/存储周期 存储器的层次化结构多级存储系…

抖音详情API:从零开始构建抖音应用

随着短视频的兴起&#xff0c;抖音已经成为了一个全球范围内的热门平台。对于开发人员而言&#xff0c;利用抖音详情API从零开始构建抖音应用具有巨大的潜力和机会。本文将为你提供从零开始构建抖音应用的指南&#xff0c;包括开发环境搭建、API请求格式、用户认证等关键环节&a…

八. 实战:CUDA-BEVFusion部署分析-环境搭建

目录 前言0. 简述1. CUDA-BEVFusion浅析2. CUDA-BEVFusion环境配置2.1 简述2.2 源码下载2.3 模型数据下载2.4 基础软件安装2.5 protobuf安装2.5.1 apt 方式安装2.5.2 源码方式安装 2.6 编译运行2.6.1 配置 environment.sh2.6.2 利用TensorRT构建模型2.6.3 编译运行程序 2.7 拓展…

【电子通识】LED有关的几个参数和定义

主波长与峰值波长 看LED灯数据手册的时候会发现有两种不同的波长参数&#xff1a;“峰值波长 Peak Wavelength”和 “主波长 Dominant Wavelength”&#xff0c;那么这两个波长代表什么&#xff1f; 峰值波长 λP&#xff08;Peak Wavelength ) : 定义为光谱辐射功率最大处所对…

JavaScript元素根据父级元素宽高缩放

/*** 等比缩放* param wrap 外部容器* param container 待缩放的容器* returns {{width: number, height: number}}* 返回值&#xff1a;width:宽度, height:高度*/aspectRatio(wrap: any, container: any) {// w h / ratio, h w * ratioconst wrapW wrap.width;const wrapH…

2023年度总结——关于如何认清自己是个FW

前言 不到各位有没有今年过得特别快的感觉。写总结时候一整理&#xff0c;我敲&#xff0c;我今年这么忙&#xff1f; 从三月份开说 三月份 这段时间刚开学&#xff0c;还算比较懵懂。不过初生牛犊不怕虎&#xff0c;那个寒假学了点怎么挖edusrc&#xff0c;开学迫不及待地…

AI按理说应该最擅长理工,为啥先冲击文艺行业?

介绍 本人数据AI工程师&#xff0c;我的观点对全行业都有冲击&#xff0c;当AI大模型持续进化之时&#xff0c;没有一家公司能独善其身。 本文从产业架构上、论文体量、基础Pass能力、通用大模型、AI开源社区、业务属性大模型、内容消费工具、创作工具赛道、企业服务这些板块…

day12--java高级编程:网络通讯

5 Day19–网络通信(Socket通信) 说明&#xff1a; io流是跟本地的文件进行数据的传输&#xff0c;读或者写。网络通信&#xff1a;数据在网络中进行的传输。 本章专题与脉络 1. 网络编程概述 Java是 Internet 上的语言&#xff0c;它从语言级上提供了对网络应用程序的支持&…