扣减机制

来源:11-3 钱包体系的基本搭建

光_cfstOQ

2025-10-05

org.qiyu.live.bank.provider.service.impl;

jakarta.annotation.;
org.idea.qiyu.live.framework.redis.starter.key.BankProviderCacheKeyBuilder;
org.qiyu.live.bank.dto.AccountTradeReqDTO;
org.qiyu.live.bank.dto.AccountTradeRespDTO;
org.qiyu.live.bank.provider.dao.maper.IQiyuCurrencyAccountMapper;
org.qiyu.live.bank.provider.dao.po.QiyuCurrencyAccountPO;
org.qiyu.live.bank.provider.service.IQiyuCurrencyAccountService;
org.qiyu.live.bank.provider.service.IQiyuCurrencyTradeService;
org.qiyu.live.bank.constants.TradeTypeEnum;
org.slf4j.Logger;
org.slf4j.LoggerFactory;
org.springframework.beans.factory.annotation.;
org.springframework.data.redis.core.RedisTemplate;
org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
org.springframework.stereotype.;
org.springframework.transaction.annotation.;

java.util.concurrent.LinkedBlockingQueue;
java.util.concurrent.ThreadPoolExecutor;
java.util.concurrent.TimeUnit;

QiyuCurrencyAccountServiceImpl IQiyuCurrencyAccountService {

    Logger = LoggerFactory.(QiyuCurrencyAccountServiceImpl.);

    RedisTemplate<String,Object> ;

    BankProviderCacheKeyBuilder ;

    IQiyuCurrencyTradeService ;

    ThreadPoolExecutor = ThreadPoolExecutor(
            ,        ,                   ,                    TimeUnit.,      LinkedBlockingQueue<>() );
    IQiyuCurrencyAccountMapper ;
    ThreadPoolTaskExecutor ;

    (userId) {
        {
            QiyuCurrencyAccountPO accountPO = QiyuCurrencyAccountPO();
            accountPO.setUserId(userId);
            .insert(accountPO);
            ;
        }(Exception e)
        {

        }
        ;
    }

    (userId, num) {
        String cacheKey = .buildUserBalance(userId);
        (.hasKey(cacheKey))
        {
            .opsForValue().increment(cacheKey,num);
            .expire(cacheKey,,TimeUnit.);
        }
        .execute(Runnable() {
            () {
                consumeIncrDBHandler(,);
            }
        });

    }

    (rollbackFor = Exception.)
    (userId, num) {
        .incr(userId,num);
        .insertOne(userId,num, TradeTypeEnum..getCode());
    }

    (userId, num) {
        String cacheKey = .buildUserBalance(userId);
        (.hasKey(cacheKey))
        {
            Long result = .opsForValue().decrement(cacheKey,num);
            .expire(cacheKey,,TimeUnit.);
            result > ;
        }
        .execute(Runnable() {
            () {
                consumeDecrDBHandler(, );
            }
        });
        ;
    }

    (rollbackFor = Exception.)
    (userId, num) {
        .decr(userId, num);
        .insertOne(userId, num * -, TradeTypeEnum..getCode());
    }

    Integer (userId) {
        String cacheKey = .buildUserBalance(userId);
        Object cacheBalance = .opsForValue().get(cacheKey);
        (cacheBalance != )
        {
            ((Integer) cacheBalance == -)
            {
                ;
            }
            (Integer) cacheBalance;
        }
        Integer currentBalance = .queryBalance(userId);
        (currentBalance == )
        {
            .opsForValue().set(cacheKey,-,,TimeUnit.);
            ;
        }
        .opsForValue().set(cacheKey,currentBalance,,TimeUnit.);
        currentBalance;
    }

    AccountTradeRespDTO (AccountTradeReqDTO accountTradeReqDTO) {
        userId = accountTradeReqDTO.getUserId();
        num = accountTradeReqDTO.getNum();
        Integer balance = getBalance(userId);
        (balance == || balance < num)
        {
            AccountTradeRespDTO.(userId,,);
        }
        .decr(userId,num);
        AccountTradeRespDTO.(userId,);
    }

    AccountTradeRespDTO (AccountTradeReqDTO accountTradeReqDTO) {
        AccountTradeRespDTO.(-, );
    }
}

老师 这个钱包的设计,对于redis是否需要考虑原子性,避免并发竞争问题?会不会有重复扣减的情况,现在写的这个代码 这些问题是不是暂时没有考虑进去,在实际工作中,还需要增加哪些工作才能比较完善

写回答

1回答

Danny_Idea

2025-10-06

最保险的方式是所有钱包扣减行为都走MySQL 然后用事务和行锁做控制,每次扣减前都生成一个唯一的流水id,避免重复扣减行为发生。
0
2
Danny_Idea
回复
光_cfstOQ
在另一处回答了你这个问题
2025-10-12
共2条回复

SpringCloudAlibaba高并发仿斗鱼直播平台实战

SpringCloudAlibaba高并发仿斗鱼直播平台实战

443 学习 · 361 问题

查看课程