zoukankan      html  css  js  c++  java
  • 【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层,保证在事务提交成功后才释放锁!

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

  • 相关阅读:
    类变量、成员变量、实例变量、局部变量、静态变量、全局变量的解释
    String的属性和方法
    数组的扩容
    以下实例演示了如何通过 List 类的 Arrays.toString () 方法和 List 类的 list.Addall(array1.asList(array2) 方法将两个数组合并为一个数组
    数组获取最大值和最小值
    Java字符串反转
    IO-3
    IO-2
    IO流
    泛型、MAP集合
  • 原文地址:https://www.cnblogs.com/VergiLyn/p/11506756.html
Copyright © 2011-2022 走看看