zoukankan      html  css  js  c++  java
  • 并发出体验 -- 解决小规模并发下单的问题

    场景描述

      现在有这么一个业务场景,线上通过手机app下单买祈福灯,支付成功后,线下寺庙点亮。存在多个 用户同时选择同一个灯的情况出现,如下图。此时,正常情况应为一个用户下单成功,其余显示灯已被选。由于,支付和下单是单独分开的,只要focus on下单就ok了。简而言之,就是一个并发现单的问题。

    分析过程

    我们可以想到的正常下单的流程,应该是这样的:

    //1. 选择祈福灯时,先查询灯是否可用。
    //2. 选择祈福灯,例如图中的“D0000065”。
    //3. 下单业务逻辑,再次查询灯是否可用。
    if(灯可用){
        该祈福灯状态设为已购买
        生成订单记录
        相关日志记录...
    } 

      在没有并发问题发生时,上面的流程近乎完美(really?),可是,多人下单时,同时去数据库中查询灯的状态时,结果都是可用的,接下来,emmmm,你懂的。

      那么,判断灯是否可用再下单,这样的逻辑是存在问题的。解决并发下单的常规思路不外乎两种,一是加锁,二是利用队列。这里,我主要是通过对数据库加锁的方式来解决这个问题的。

     乐观锁与悲观锁

    • 乐观锁(Optimistic Lock),想法乐观,认为自己在操作数据库时不会发生冲突,取数据时不加锁,更新数据时加锁,进行判断。
    • 悲观锁(Pessimistic Lock),想法悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

      显然乐观锁的相应速度快,悲观锁的时间消耗等都比较大。对于我们这个案例,使用这两种都是可以解决的。关于乐观锁和悲观锁详情可参考【https://www.cnblogs.com/Kevin-ZhangCG/p/10272293.html】

     悲观锁

      要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。

    set autocommit=0;

    共享锁

    SELECT … LOCK IN SHARE MODE

    SELECT … LOCK IN SHARE MODE 在读取的行上设置一个共享锁,其他的session可以读这些行,但在你的事务提交之前不可以修改它们。如果这些行里有被其他的还没有提交的事务修改,你的查询会等到那个事务结束之后使用最新的值。

    排他锁

    SELECTFOR UPDATE

     索引搜索遇到的记录,SELECT … FOR UPDATE 会锁住行及任何关联的索引条目,和你对那些行执行 update 语句相同。其他的事务会被阻塞在对这些行执行 update 操作,获取共享锁,或从某些事务隔离级别读取数据等操作。一致性读(Consistent Nonlocking Reads)会忽略在读取视图上的记录的任何锁。(旧版本的记录不能被锁定;它们通过应用撤销日志在记录的内存副本上时被重建。)

    注:普通 select 语句默认不加锁,而CUD操作默认加排他锁。

    乐观锁

      乐观锁也就是在更新时进行查询,通常用一个version字段来实现。

    UPDATE ... WHERE...
    # 基于version的实现
    SELECT ..., verison FROM [table] WHERE id = #{id}
    UPDATE [table] SET..., version = version + 1 where id = #{id} AND version = #{version}

    问题解决

      在项目中,由于时间关系没有使用基于version方式的乐观锁,而是直接采用了update ... where的方式。直接对当前的灯号进行查询,如果可用就立刻更新灯的状态为不可用,相当于加共享锁。如果发生并发的情况,同时用update语句,数据库也会自动加上X锁,因此最终只有一个用户可以下单成功。
    下单流程:

    public int saveOrder(){
        // 执行update ... where
        boolean isAvaliable;
        isAvaliable = denginfoService.updateDengAnyway();
        if (isAvaliable) {
            //下单的业务逻辑
        }
    }
    
    public boolean updateDengAnyway(String ccode, List<String> dengid) {
        //判断灯是否可用
        String hql = "update DenginfoEntity set ordertype =2 where ccode=? and dengid=? and (ordertype=0 or (ordertype=1 and ordertime<?))";
        Query query;int flag;
        try {
            for (String deng : dengid) {
                query = getSession().createQuery(hql);
                //参数化赋值
                flag = query.executeUpdate();
                logger.info("update---  " + flag);
                if (flag == 0)
                    return false;
            }
        } catch (HibernateException | NullPointerException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

     原文参考【http://www.cnblogs.com/Sinte-Beuve/p/7631745.html】

     

  • 相关阅读:
    Call KernelIoControl in user space in WINCE6.0
    HOW TO:手工删除OCS在AD中的池和其他属性
    关于新版Windows Server 2003 Administration Tools Pack
    关于SQL2008更新一则
    微软发布3款SQL INJECTION攻击检测工具
    HyperV RTM!
    OCS 2007 聊天记录查看工具 OCSMessage
    CoreConfigurator 图形化的 Server Core 配置管理工具
    OC 2007 ADM 管理模板和Live Meeting 2007 ADM 管理模板发布
    Office Communications Server 2007 R2 即将发布
  • 原文地址:https://www.cnblogs.com/Kevin-ZhangCG/p/10275396.html
Copyright © 2011-2022 走看看