从一段代码说起锁和事务

锁是为了解决高并发产生的多线程对共享资源进行并发访问时,由于后端接口『来不及』处理线程请求的数据,导致最终出现数据不一致或并非预想的结果,比如常见的『抢购商品超卖』、『手机号重复注册』等等。

以这个方法为例展示抢购逻辑:

    @Transactional(rollbackFor = Exception.class)
    public void robWithZKLock(BookRobDto dto) throws Exception{
        //创建ZooKeeper互斥锁组件实例,需要将CuratorFramework实例、精心构造的共享资源 作为构造参数
        InterProcessMutex mutex=new InterProcessMutex(client,pathPrefix+dto.getBookNo()+dto.getUserId()+"-lock");
        try {
            //采用互斥锁组件尝试获取分布式锁-其中尝试的最大时间在这里设置为15s
            if (mutex.acquire(15L, TimeUnit.SECONDS)){
                //真正的核心处理逻辑

                //根据书籍编号查询记录
                BookStock stock=bookStockMapper.selectByBookNo(dto.getBookNo());
                //统计每个用户每本书的抢购数量
                int total=bookRobMapper.countByBookNoUserId(dto.getUserId(),dto.getBookNo());

                //商品记录存在、库存充足,而且用户还没抢购过本书,则代表当前用户可以抢购
                if (stock!=null && stock.getStock()>0 && total<=0){
                    log.info("---处理书籍抢购逻辑-加ZooKeeper分布式锁---,当前信息:{} ",dto);

                    //当前用户抢购到书籍,库存减一
                    int res=bookStockMapper.updateStock(dto.getBookNo());
                    //更新库存成功后,需要添加抢购记录
                    if (res>0){
                        //创建书籍抢购记录实体信息
                        BookRob entity=new BookRob();
                        //将提交的用户抢购请求实体信息中对应的字段取值
                        //复制到新创建的书籍抢购记录实体的相应字段中
                        entity.setUserId(dto.getUserId());
                        entity.setBookNo(dto.getBookNo());
                        //设置抢购时间
                        entity.setRobTime(new Date());
                        //插入用户注册信息
                        bookRobMapper.insertSelective(entity);
                    }
                }else {
                    //如果不满足上述的任意一个if条件,则抛出异常
                    throw new Exception("该书籍库存不足!");
                }

            }else{
                throw new RuntimeException("获取ZooKeeper分布式锁失败!");
            }
        }catch (Exception e){
            throw e;
        }finally {
            //TODO:不管发生何种情况,在处理完核心业务逻辑之后,需要释放该分布式锁
            mutex.release();
        }
    }

可以看到,mutex 的存在让同一时刻只能有一个线程进入逻辑,解决了超卖的问题。

要是单机的话可以使用 Java 语言层面的并发原语入 sync 解决,但是只能解决单机的问题,多机还是要靠分布式锁。所以,直接使用分布式锁应该是一步到位了。

分布式锁

你可以会问,用 synchronized 这样的 Java 锁不行么?在单机时代,可以,但是现在,不行。因为现在都是多实例服务,synchronized 这样的锁只能保证一个 JVM 上在同一时刻只能有一个线程进入。但是在分布式架构下,资源共享不再是传统的线程共享,而是跨 JVM 进程之间资源的共享了,只能使用 ZK 这样的分布式锁来解决。

事务

还是上面的代码,你会看到 @Transactional 这个 Spring 事务,这也是单机的事务,保证这个方法下的 ACID。

分布式事务

那分布式事务呢?你可以看到,这个方法下都是直接调用数据库,而不是调用别人的微服务,这就可以只使用单机事务。试想一下,如果你要调用存库服务、支付服务,而不是调用数据库,这时候就只能使用分布式事务了。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容