CAS+失败重试方式实现数据库的原子性更新

基础组件(四):原子操作CAS与锁实现 高性能组件: 原子操作CAS与锁实现 阅读详情

在数据库修改单条数据时,常用的方式是select for update的悲观锁机制,如果锁竞争比较大,没有获得锁的操作会阻塞。使用CAS乐观锁的方式,可以大大提高并发性。例如,在分布式服务中,多个用户并发下单操作前会先扣减库存时,网上盗图,服务1,服务2和服务3为不同机器上的库存服务。库存扣减操作流程如下:

多个用户并发操作服务

使用cas方式的乐观锁,当库存还剩3个,3个用户同时下单,服务同时扣减库存,可以并发地扣减成功,提高了并发性。如果库存还剩1个,3个用户同时下单,同时扣减库存,这时只有1个用户会操作成功,其余2个会失败,避免了超卖。

在库存的CAS操作中,先进行库存查询操作,然后根据value+version方式修改库存,如果操作失败,则幂等地重复操作。通常会从操作次数和执行时间两个条件限制CAS的操作,如果两个条件中有某个条件触发,可以抛出乐观锁异常。

在Java项目中,通过AOP+注解的方式实现数据库操作的CAS幂等操作的统一处理。定义OptimisticRetry注解,标识CAS重试Spring AOP的切点,并且设置属性value(最大重试次数条件),以及属性maxExecuteTime(最大执行时间条件)。OptimisticRetryAOP定义CAS重试的切面,乐观锁的实现。OptimisticRetry注解定义代码如下:


/**
 * 乐观锁的重试
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OptimisticRetry {

    /**
     * 最大重试次数
     */
    int value() default 200;

    /**
     * 最大执行时间
     */
    int maxExecuteTime() default 2 * 60 * 60 * 1000;
}

OptimisticRetryAOP切面,定义标注了OptimisticRetry注解的方法则进行,CAS幂等重试,如果达到最大重试次数限制或者最大执行时间限制,则抛出乐观锁异常OptimisticLockingFailureException。代码如下:


/**
 * 乐观锁的重试
 **/
@Aspect
@Component
@Order(10000)
@Slf4j
public class OptimisticRetryAOP {

    @Pointcut("@annotation(com.smcx.winemall.common.lock.OptimisticRetry)|| @within(com.smcx.winemall.common.lock.OptimisticRetry)")
    public void optimisticRetryPointcut() {

    }

    @Around("optimisticRetryPointcut()")
    public Object doConcurrentOperation(ProceedingJoinPoint joinPoint) throws Throwable {

        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        // 代理目标对象Class
        Class targetClazz = joinPoint.getTarget().getClass();
        Method method = targetClazz.getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
        Optional<OptimisticRetry> optimisticRetryOption = getOptimisticRetry(method.getAnnotations());
        if (!optimisticRetryOption.isPresent()) {

            log.warn("no OptimisticRetry Annotation to execute!");
            return joinPoint.proceed();
        }
        OptimisticRetryConfig optimisticRetryConfig = obtainOptimisticRetryConfig(optimisticRetryOption.get());
        int numAttempts = 0;
        OptimisticLockingFailureException lockFailureException;
        long startTime = System.currentTimeMillis();
        do {
            numAttempts++;
            try {
                return joinPoint.proceed();
            } catch (OptimisticLockingFailureException ex) {

                lockFailureException = ex;
                long executeTime = System.currentTimeMillis() - startTime;
                long maxExecuteTime = optimisticRetryConfig.getMaxExecuteTime();
                if (isLargerThanMaxExecuteTime(executeTime, maxExecuteTime)) {

                    log.warn("throw optimistic locking failure exception!num attempts [{}],start time [{}]," +
                                    "actual execute time [{}] ms, max execute time [{}] ms",
                            numAttempts, startTime, executeTime, maxExecuteTime);
                    throw lockFailureException;
                }
            }
        }
        while (numAttempts <= optimisticRetryConfig.getMaxTryCount());

        log.warn("throw optimistic locking failure exception!num attempts {} ", numAttempts);
        throw lockFailureException;
    }

    /**
     * 大于限制的重试执行时间
     *
     * @param executeTime
     * @return
     */
    private boolean isLargerThanMaxExecuteTime(long executeTime, long maxExecuteTime) {

        if (executeTime <= 0) {
            return false;
        }
        if (maxExecuteTime > executeTime) {
            return true;
        }
        return false;
    }

    private OptimisticRetryConfig obtainOptimisticRetryConfig(OptimisticRetry optimisticRetry) {

        // 从注解中获取配的值
        OptimisticRetryConfig optimisticRetryConfig = new OptimisticRetryConfig();
        optimisticRetryConfig.setMaxTryCount(optimisticRetry.value());
        optimisticRetryConfig.setMaxExecuteTime(optimisticRetry.maxExecuteTime());
        return optimisticRetryConfig;
    }

    private Optional<OptimisticRetry> getOptimisticRetry(Annotation[] annotations) {

        if (ArrayUtils.isEmpty(annotations)) {
            return Optional.empty();
        }
        for (Annotation anno : annotations) {
            if (anno.annotationType().getName().equals(OptimisticRetry.class.getName())) {
                return Optional.of((OptimisticRetry) anno);
            }
        }
        return Optional.empty();
    }

    /**
     * 重试配置
     */
    @Data
    private static class OptimisticRetryConfig implements Serializable {

        private static final long serialVersionUID = -182211651320526367L;

        /**
         * 最大重试次数
         */
        private int maxTryCount;

        /**
         * 最大执行时间
         */
        private long maxExecuteTime;
    }
}

业务代码中操作,代码如下:

    @Override
    @Transactional(rollbackFor = Exception.class)
    @OptimisticRetry
    public void decreaseStockRemainingAmount(OperateStockDto operateStock) {

        // 获取存储
        WinemallStock existStock = winemallStockMapper.selectByOperateStock(operateStock);
        // TODO 库存是否充足判断等条件判断

        // 修改库存数据
        if (0 == winemallStockMapper.updateStockRemainingAmount(existStock, 1)) {
            throw new OptimisticLockingFailureException("decrease stock remaining amount optimistic locking failure!");
        }
    }

 

CAS单点登录系统数据库支持组件实战指南 本文还有配套的精品资源,点击获取 简介:CAS(Central Authentication Service)是一个广泛应用于Web应用的单点登录(SSO)系统,其中CAS3版本带来了增强的安全性和功能。该组件"cas-server-support-jdbc-3.3.1.rar"特别提供了数据库支持,允许CAS与关系型数据库交互,存储用户认证信息等。本组件包括数据库连接配置... 阅读详情

相关推荐

如何导入su_利用GIS快速生成SU场地模型

01|前言:进入2021年了,希望大家也是一个新的开始。告诫自己多写文章多更新,多读点书多旅行。然而元旦由于出去玩了4天,积累了一些事情要处理,一直没有时间好好写点东西。今天给大家带来的分享主题叫“利用GIS快速生成SU场地模型”。相关的视频方法其实B站上也有,但我相信视频很多人都不会看,我自己也是。直接给大家上操作流程。主要的操作步骤是【获取场地的矢量shp文件】—【导入ArcScen...

weixin_33806859的博客 9813

java代码示例:采用 CAS + 失败重试来保障更新操作的原子性

需要注意的是,虽然CAS操作可以保障更新操作的原子性,但在高并发环境下,频繁的失败重试可能会导致性能下降。因此,在选择使用CAS和失败重试时,需要根据具体场景评估并发访问的情况和性能要求。这种方式使用CAS和失败重试来保障更新操作的原子性,即只有一个线程能成功更新计数器的值,其他线程会进行重试直到成功。方法)尝试更新计数器的值。如果CAS操作失败,说明有其他线程已经修改了计数器的值,此时线程会进行失败重试,直到更新成功或达到最大重试次数(作为计数器,并创建两个线程来对计数器进行更新操作。

学亮编程手记 838

FreeRTOS移植实战:从零开始在STM32上搭建多任务系统(含Proteus仿真配置)

本文详细介绍了如何在STM32平台上从零开始移植FreeRTOS,并搭建多任务系统,包含Proteus仿真配置。通过环境准备、源码移植、多任务设计到仿真调试的全流程实战,帮助开发者快速掌握FreeRTOS在STM32上的应用技巧,提升嵌入式开发效率。

weixin_33737134的博客 232

化工产品CAS号查询数据库(共215027条数据)

CAS号是公开的化学物质信息的最权威的集合。本化工产品CAS号查询数据库共有215027条数据,包含中文名称、英文名、CAS号、分子式、分子量、密度、沸点、熔点等基本字段。

weixin_1002198586的博客 2380

【滤波跟踪】多雷达协同下的多目标跟踪:航迹管理优化与卡尔曼滤波融合策略

本文深入探讨了多雷达协同下的多目标跟踪技术,聚焦于航迹管理优化与卡尔曼滤波融合策略两大核心。通过分析分布式融合架构、航迹关联算法(如匈牙利算法)以及应对非线性与机动目标的扩展卡尔曼滤波(EKF)与无迹卡尔曼滤波(UKF)等方法,旨在解决多源信息融合难题,提升复杂环境下目标跟踪的精度与鲁棒性。

weixin_29214199的博客 281

如何创建属于自己的大语言模型:从零开始的指南

随着人工智能的快速发展,大语言模型(LLM)在各种场景中表现出了卓越的能力,例如文本生成、对话交互和内容总结等。本文将带你了解创建 LLM 的核心步骤,从理论到实践,让你拥有一个自己的智能助手。

赵大仁的博客 3281

CAS单点登录连接用户数据库

打开cas-overlay-template-5.3\src\main\resources\application.properties文件。

Back_to_the_past的博客 1098

【最佳实践】使用 CAS 方式并发插入或更新数据到数据库 2021-08-17

CAS 并发更新或者插入一条数据使用场景Java 核心代码示例MySQL 业务表信息 使用场景 消费消息队列MQ的消息,持久化到DB时。 可能会有并发操作的业务中。 Java 核心代码示例 import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomUtils; import org.springframework.dao.DuplicateKeyException; import org.springframework

小方编程的专栏 1659

为什么传统CNN在视网膜分割中败给Mamba?从血管拓扑学揭秘Serp-Mamba的蛇形扫描黑科技

本文深入分析了传统CNN在视网膜血管分割任务中的局限性,特别是在处理高分辨率图像和复杂血管拓扑结构时。针对这些问题,文章重点介绍了Serp-Mamba模型的核心创新——蛇形交织自适应扫描(SIA)机制,该机制通过让扫描路径自适应地贴合血管曲率,并结合模糊驱动双重校准(ADDR)模块应对极端类别失衡,从而在视网膜血管分割任务上实现了性能的显著突破。

ss78901的博客 894

[漏洞挖掘与防护] 03.漏洞利用之WinRAR安全缺陷复现(CVE-2018-20250)及软件自启动分析

上一篇文章将详细介绍MS08-067远程代码执行漏洞(CVE-2008-4250)及防御过程,它是Windows Server服务RPC请求缓冲区溢出漏洞,利用445端口,并通过Metasploit工具获取shell及进行深入的操作。这篇文章将详细讲解WinRAR漏洞(CVE-2018-20250),并复现了该漏洞和讲解了恶意软件自启动劫持原理。基础性文章,希望对入门的同学有帮助。

杨秀璋的专栏 1090

CAS+失败重试方式实现数据库的原子性更新(1)

作为一名即将求职的程序员,面对一个可能跟近些年非常不同的 2019 年,你的就业机会和风口会出现在哪里?在这种新环境下,工作应该选择大厂还是小公司?已有几年工作经验的老兵,又应该如何保持和提升自身竞争力,转被动为主动?就目前大环境来看,跳槽成功的难度比往年高很多。一个明显的感受:今年的面试,无论一面还是二面,都很考验Java程序员的技术功底。最近我整理了一份复习用的面试题及面试高频的考点题及技术点梳理成一份“Java经典面试问题(含答案解析).pdf和一份网上搜集的“Java程序员面试笔试真题库.pdf。

2401_84049061的博客 832

基于STM32设计的老人摔倒检测报警系统(OneNet+MQTT)(235)

基于STM32设计的老人摔倒检测报警系统,不仅能够实时监测老人的位置信息,方便在紧急情况下快速定位,还能通过陀螺仪模块精确检测老人的身体姿态,一旦判断为摔倒状态,系统将立即触发蜂鸣器报警,并通过4G网络向指定联系人发送短信警示,确保老人能够迅速得到援助。此外,系统还支持老人主动按下设备上的按键发送求助短信,增强了系统的灵活性和实用性。

4692

CAS+失败重试方式实现数据库的原子性更新,入职阿里啦

*** 乐观锁的重试**/@Aspect@Component@Slf4j// 代理目标对象Classif (!”);do {try {/*** 大于限制的重试执行时间* @return*/// 从注解中获取配的值。

2401_83621499的博客 985

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

面试必问的CAS,你懂多少?

面试必问的CAS,你懂多少?

酷酷的馫博客 1万+

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

AtomicInteger底层实现原理是什么?如何在自己的产品代码中应用CAS操作?

AtomicInteger是Java中用于实现原子操作的类,基于CAS(Compare-And-Swap)技术确保线程安全。CAS通过比较当前值与预期值,若一致则更新,否则重试或返回失败。AtomicInteger内部依赖Unsafe类进行底层操作,并使用volatile修饰的value字段保证可见性。其原子操作如getAndIncrement通过Unsafe的getAndAddInt方法实现,失败时重试。CAS是Java并发中无锁机制的基础,适用于高并发场景。Java提供了Atomic包和Variable

哀莫大于心死 1121

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

谈一谈CAS实现

1. 什么是乐观锁与悲观锁? 悲观锁 总是假设最坏的情况,每次读取数据的时候都默认其他线程会更改数据,因此需要进行加锁操作,当其他线程想要访问数据时,都需要阻塞挂起。 悲观锁的实现: 1. 传统的关系型数据库使用这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁; 2. Java里面的同步 synchronized 关键字的实现。 乐观锁 乐观锁,其实就是一种思想,总是认为不会产生并发问题,每次读取数据的时候都认为其他线程不会修改数据,所以不上锁...

xiaoxu9522的博客 1661

CAS更新

CAS(Compare and Swap)是一种无锁同步机制,通过比较当前值是否等于预期值来决定是否更新,实现原子操作。其核心原理是先比较再交换,数据库中使用类似update ... where语句实现。相比悲观锁,CAS在低冲突场景性能更高,但存在ABA问题(值被改回原值),可通过版本号或时间戳解决。CAS优点包括无阻塞、无死锁和轻量级,适合读多写少场景;缺点是自旋开销大且只能保证单变量原子性。最佳实践包括限制重试次数、退避策略和单向状态设计等。

lotus_csdn888的博客 207

为什么你的缓存更新总是出错?ConcurrentHashMap computeIfAbsent原子性揭秘

解决缓存更新异常难题,深入解析ConcurrentHashMap的computeIfAbsent原子性机制。适用于高并发场景下的线程安全缓存构建,揭示方法内部如何保证计算与写入的原子性,避免重复计算与数据错乱,提升系统稳定性,值得收藏。

AlgoInk的博客 300

C++ CAS

它通过比较内存中的值和预期值,如果两者相等,则将内存中的值更新为新值;如果不相等,则表示该值已经被其他线程修改,操作失败。整个比较和交换的过程是原子的,即在执行过程中不会被其他线程中断。CAS 即 Compare-And-Swap,是一种用于实现多线程同步的原子操作。CAS 操作通常是由硬件层面提供支持,例如在 x86 架构中,有专门的指令来实现 CAS 操作,这保证了操作的原子性。CAS 操作通常需要三个参数:内存地址(V)、预期值(A)和新值(B)。

doubleintfloat的博客 802

【数据库7】理解数据库事务乐观锁的一种实现方式——CAS

注明: 本文转载自http://www.hollischuang.com/archives/1537在深入理解乐观锁与悲观锁一文中我们介绍过锁。本文在这篇文章的基础上,深入分析一下乐观锁的实现机制,介绍什么是CAS、CAS的应用以及CAS存在的问题等。线程安全众所周知,Java是多线程的。但是,Java对多线程的支持其实是一把双刃剑。一旦涉及到多个线程操作共享资源的情况时,处理不好就可能产生线程安...

大洋孩的博客 801

LA664上CAS重试死循环导致事务回滚与数据丢失的深度复盘

并发编程中,原子操作是保证数据一致性的基础。从x86的cmpxchg到LoongArch的LL/SC,不同架构的原子指令实现与失败语义差异显著。CAS(比较并交换)虽能提供原子更新能力,但仅覆盖单次比较与写入,其后的重试、期望值刷新等决策仍依赖开发者正确编码。若在循环内未重新读取真实值,高并发下LL/SC窗口期竞争将触发CAS重试死循环,导致CPU飙高、线程卡死,进而拖垮数据库连接池与事务,最终引发数据丢失。此类问题常见于国产化服务器(如LA664芯片)上的批量归档、订单状态更新等场景。本文以真实故障为线索

weixin_34183910的博客 508
上一篇: 事件驱动模式-Tomcat和zookeeper教你如何玩
下一篇: 基于mybatis 类型处理器typeHandlers实现数据库数据到业务对象的转换
iloveoverfly
博客等级 码龄14年 29粉丝 50原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值