• 【redisson】分布式锁与数据库事务


    场景:
      用户消耗积分兑换商品。

    user_point(用户积分):

    id point
    1 2000

    point_item(积分商品):

    id point num
    101 200 10

    传统的controller、service、dao三层架构,数据库事务控制在service层(数据库MYSQL)。

    @RestController
    @RequestMapping(value = {"point"})
    public class UserPointController{
        @Autowired
        private UserPointService userPointService;
    
        @RequestMapping("/exchange")
        public boolean exchange(HttpServletRequest request, Long userId, Long itemId){
    
            return userPointService.exchange(userId, itemId);
        }
    }
    
    @Service
    public class UserPointService {
        @Resource
        private RedissonClient redissonClient;
    
        @Transaction
        public boolean exchange(Long userId, Long itemId) throws Exception {
            RLock lock = redissonClient.getLock("lock:" + itemId);
            try {
                boolean bool = lock.tryLock(10, 30, TimeUnit.SECONDS);
                if (!bool){
                    throw new Exception("操作失败,请稍后重试");
                }
    
                UserPoint user = "select * from user_point where id = :userId";
                PointItem item = "select * from point_item where id = :itemId";
    
                if(user.point - item.point > 0 && item.num > 0){
                    // 扣减积分
                    >> update user_point set point = point - :item.point where id = :userId; 
    
                    // 扣减库存
                    >> update point_item set num = num - 1 where id = :itemId; 
        
                    return true;
                }
    
                return false;
            } catch (Exception e) {
                throw e;
            } finally {
                if(lock != null && lock.isHeldByCurrentThread()){
                    lock.unlock();
                }
            }
        }
    
    }
    

    观察以上代码思考:

    1. lock是什么时候释放的?
        调用lock.unlock()就是释放redisson-lock。

    2. 事务是什么时候提交的?
        事务的提交是在方法UserPointService#exchange()执行完成后。所以,示例代码中其实会先释放lock,再提交事务

    3. 事务是什么时候提交完成的?
        事务提交也需要花费一定的时间

    由于先释放lock,再提交事务。并且由于mysql默认的事务隔离级别为 repetable-read,这导致的问题就是:
    假设现在有2个并发请求{"userId": 1, "itemId": 101},user剩余积分201。
    假设A请求先获得lock,此时B请求等待获取锁。
    A请求得到的数据信息是user_point#point=201,此时允许兑换执行扣减,返回true。
    在返回true前,会先释放lock,再提交事务。

    释放lock后,B请求可以马上获取到锁,查询user可能得到剩余积分: 201(正确的应该是剩余积分: 1),因为A请求的事务可能未提交完成造成!

    解决方案:
    暂时是将lock改写到controller层,保证在事务提交成功后才释放锁!

    (画图苦手,时序图有缘再见)

  • 相关阅读:
    Linux下的C编程实战之文件系统编程
    IPMSG在ubuntu linux上的安装
    NTP服务器
    [Multimedia][MPEG2]MPEG2系统原理
    BitOperation.cs
    android interview 1
    Android USB Host
    android interview 3
    someone's android note
    Android Afianl框架(1)——FinalActivity注解
  • 原文地址:https://www.cnblogs.com/VergiLyn/p/11506756.html
Copyright © 2020-2023  润新知