切面秒杀锁Lock

简介: 前言在单体架构的秒杀活动中,为了减轻DB层的压力,这里我们采用了Lock锁来实现秒杀用户排队抢购。然而很不幸的是尽管使用了锁,但是测试过程中仍然会超卖,执行了N多次发现依然有问题。

前言

在单体架构的秒杀活动中,为了减轻DB层的压力,这里我们采用了Lock锁来实现秒杀用户排队抢购。然而很不幸的是尽管使用了锁,但是测试过程中仍然会超卖,执行了N多次发现依然有问题。输出一下代码吧,可能大家看的比较真切:

@Service("seckillService")
public class SeckillServiceImpl implements ISeckillService {
    /**
     * 思考:为什么不用synchronized
     * service 默认是单例的,并发下lock只有一个实例
     */
    private Lock lock = new ReentrantLock(true);//互斥锁 参数默认false,不公平锁
    @Autowired
    private DynamicQuery dynamicQuery;

    @Override
    @Transactional
    public Result  startSeckilLock(long seckillId, long userId) {
         try {
            lock.lock();
            //这里、不清楚为啥、总是会被超卖101、难道锁不起作用、lock是同一个对象
            String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
            Object object =  dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
            Long number =  ((Number) object).longValue();
            if(number>0){
                nativeSql = "UPDATE seckill  SET number=number-1 WHERE seckill_id=?";
                dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
                SuccessKilled killed = new SuccessKilled();
                killed.setSeckillId(seckillId);
                killed.setUserId(userId);
                killed.setState(Short.parseShort(number+""));
                killed.setCreateTime(new Timestamp(new Date().getTime()));
                dynamicQuery.save(killed);
            }else{
                return Result.error(SeckillStatEnum.END);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        return Result.ok(SeckillStatEnum.SUCCESS);
    }
}

代码写在service层,bean默认是单例的,也就是说lock肯定是一个对象。感觉不放心,还是打印一下 lock.hashCode(),输出结果没问题。由于还有其他事情要做,最终还是带着疑问提交代码到码云。

追踪

如果想分享代码并使大家一起参与进来,一定要自荐,这样才会被更多的人发现。当然,如果有交流群一定要留下联系方式,这样讨论起来可能更方便。项目被推荐后,果然加群的小伙伴就多了。由于项目配置好相应参数就可以测试,并且每个点都有相应的文字注释,其中有心的小伙伴果然注意到了我写的注释<这里、不清楚为啥、总是会被超卖101、难道锁不起作用、lock是同一个对象>,然后提出了困扰自己好多天的问题。

码友zoain说,测试了好久终于发现了问题,原来lock锁是在事物单元中执行的。看到这里,小伙伴们有没有恍然大悟,反正我是悟了。这里,总结一下为什么会超卖101:秒杀开始后,某个事物在未提交之前,锁已经释放(事物提交是在整个方法执行完),导致下一个事物读取到了上个事物未提交的数据,也就是传说中的脏读。此处给出的建议是锁上移,也就是说要包住整个事物单元。

AOP+锁

为了包住事物单元,这里我们使用AOP切面编程,当然你也可以上移到Control层。

自定义注解Servicelock:

@Target({ElementType.PARAMETER, ElementType.METHOD})    
@Retention(RetentionPolicy.RUNTIME)    
@Documented    
public  @interface Servicelock { 
     String description()  default "";
}

自定义切面LockAspect:

@Component
@Scope
@Aspect
public class LockAspect {
    /**
     * 思考:为什么不用synchronized
     * service 默认是单例的,并发下lock只有一个实例
     */
    private static  Lock lock = new ReentrantLock(true);//互斥锁 参数默认false,不公平锁  

    //Service层切点     用于记录错误日志
    @Pointcut("@annotation(com.itstyle.seckill.common.aop.Servicelock)")  
    public void lockAspect() {

    }

    @Around("lockAspect()")
    public  Object around(ProceedingJoinPoint joinPoint) { 
        lock.lock();
        Object obj = null;
        try {
            obj = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        } finally{
            lock.unlock();
        }
        return obj;
    } 
}

切入秒杀方法:

@Service("seckillService")
public class SeckillServiceImpl implements ISeckillService {
    /**
     * 思考:为什么不用synchronized
     * service 默认是单例的,并发下lock只有一个实例
     */
    private Lock lock = new ReentrantLock(true);//互斥锁 参数默认false,不公平锁
    @Autowired
    private DynamicQuery dynamicQuery;

    @Override
    @Servicelock
    @Transactional
    public Result startSeckilAopLock(long seckillId, long userId) {
        //来自码云码友<马丁的早晨>的建议 使用AOP + 锁实现
        String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
        Object object =  dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
        Long number =  ((Number) object).longValue();
        if(number>0){
            nativeSql = "UPDATE seckill  SET number=number-1 WHERE seckill_id=?";
            dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
            SuccessKilled killed = new SuccessKilled();
            killed.setSeckillId(seckillId);
            killed.setUserId(userId);
            killed.setState(Short.parseShort(number+""));
            killed.setCreateTime(new Timestamp(new Date().getTime()));
            dynamicQuery.save(killed);
        }else{
            return Result.error(SeckillStatEnum.END);
        }
        return Result.ok(SeckillStatEnum.SUCCESS);
    }
}

所有的工作完成以后,我们来测试一下代码,意料之中,再也没有出现超卖的现象。然而,你以为就这么结束了么?细心的码友IM核米,又提出了以下问题:Spring 里的切片在未指定排序的时候,两个注解是随意执行的。如果事务在加锁前执行的话,是不是就会产生问题?

首先,由于自己实在没有时间去取证,最终还是码友IM核米完成了自问自答,这里引用下他的解释:

我说的没错,但 @Transactional 切片是特殊情况

1)多 AOP 之间的执行顺序在未指定时是 :undefined ,官方文档并没有说一定会按照注解的顺序进行执行,只会按照 @ Order 的顺序执行。

可参考官方文档: 可以在页面里搜索 Command+F「7.2.4.7 Advice ordering」https://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-ataspectj-advice-ordering

2)事务切面的 default Order 被设置为了 Ordered.LOWEST_PRECEDENCE,所以默认情况下是属于最内层的环切。

可参考官方文档: 可以在页面里搜索 Command+F「Table 10.2. tx:annotation-driven/ settings」 https://docs.spring.io/spring/docs/3.0.x/reference/transaction.html#transaction-declarative-txadvice-settings

总结

经验真的很重要,踩的坑多了也变走成了路
不要吝啬自己的总结成果,分享交流才能够促使大家共同进步
最好不要怀疑久经考验的Lock锁同志,很有可能是你使用的方式不对

思考

相关文章
|
XML Java Maven
【Maven技术专题】「实战开发系列」盘点Maven项目中打包需要注意到的那点事儿
【Maven技术专题】「实战开发系列」盘点Maven项目中打包需要注意到的那点事儿
298 1
|
6月前
|
人工智能 自然语言处理 PyTorch
Chat2SVG – 文本描述实现高质量矢量图形的生成框架
Chat2SVG 是一个创新的文本到矢量图形生成框架,结合大型语言模型和图像扩散模型,通过多阶段流程生成高质量的 SVG 图形,支持自然语言指令编辑,适用于设计、教育和艺术创作等领域。
199 8
Chat2SVG – 文本描述实现高质量矢量图形的生成框架
|
10月前
|
机器学习/深度学习 自然语言处理 算法
通义灵码开发者社区的构成——开发者群体
通义灵码开发者社区汇聚了来自不同背景的开发者,包括专业软件工程师、数据科学家、学生和业余开发者等,他们通过代码贡献、文档编写和社区活跃参与,共同推动社区的创新与发展,为通义灵码的广泛应用提供了坚实基础。
通义灵码开发者社区的构成——开发者群体
|
7月前
|
图形学 开发者
unity 从工具栏拖动生成物体
在 Unity 中实现从工具栏拖动生成物体的功能,基于编辑器扩展、事件系统和预制体实例化。通过自定义编辑器窗口、处理鼠标事件(按下、移动、释放)及使用 Instantiate 方法,可实现拖动并生成预制体物体。代码示例展示了如何检测鼠标事件并在指定位置实例化物体。
|
SQL 关系型数据库 MySQL
使用SQL语句创建数据库:一步步指南
使用SQL语句创建数据库:一步步指南
588 0
|
11月前
|
人工智能 安全 搜索推荐
2024年度云治理企业成熟度发展报告解读(四)支柱结构关系及案例分享
从组织、成本、稳定、安全到性能、效率这样一个路径,我们一定要有策略地完成各个支柱之间的互相配合。云治理不仅仅是技术问题,而是需要多团队合作和管理策略的制定。
2024年度云治理企业成熟度发展报告解读(四)支柱结构关系及案例分享
|
存储 安全 物联网
未来科技潮流之下:探索区块链技术、物联网与虚拟现实的融合应用
在数字化时代的浪潮中,新兴技术如区块链、物联网(IoT)、和虚拟现实(VR)正逐渐渗透至社会的各个层面。本文将通过数据驱动的分析方法,探讨这些技术各自的发展趋势以及它们如何相互交织形成创新的应用场景。我们将深入解读最新的科研数据,并结合逻辑推理,揭示这些技术在未来可能带来的变革性影响。
141 31
|
机器学习/深度学习 人工智能 自然语言处理
AI基础科普:揭开人工智能的神秘面纱
人工智能(Artificial Intelligence, AI)已经成为现代科技的热门话题,影响着我们的生活方方面面。从语音助手到自动驾驶汽车,AI正在以惊人的速度改变着世界。然而,对于许多人来说,AI仍然是一个模糊的概念。本文将通过通俗易懂的语言和丰富的图文,全面介绍AI的基础知识,帮助读者更好地理解这个激动人心的领域。
|
编译器 C语言 C++
【51单片机】LED的三个基本项目(LED点亮&LED闪烁&LED流水灯)(3)
【51单片机】LED的三个基本项目(LED点亮&LED闪烁&LED流水灯)(3)
|
前端开发 NoSQL JavaScript
RAP2-DELOS 开源社区版本 (后端 API 服务器)
RAP2-DELOS 开源社区版本 (后端 API 服务器)
575 0

热门文章

最新文章