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

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

user_point(用户积分):redis

id point
1 2000

point_item(积分商品):sql

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,再提交事务app

  3. 事务是何时提交完成的?   事务提交也须要花费必定的时间code

因为先释放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请求的事务可能未提交完成形成!get

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

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

相关文章
相关标签/搜索