题目

背景描述:
在现今电子商务场景下,预算管理在很多场景下都有广泛应用,比如营销奖品,积分、红包的发放、商品库存管理等。预算管理要求消耗的物品的数量能够按照预算库存进行消耗,不能超额,尽可能的保障预算的消耗率。
大规模请求量以及分布式环境下,预算管理问题更加突出,如果预算控制不合理,则会出现物品超发或是预算消耗率不高的情况,超发会导致直接的资金损失出现;预算消耗率不高则会导致商品无法卖出、奖品无法发放等问题,从而导致间接的资金损失。

编程要求:

根据上面的背景,用伪代码描述如下功能,在分布式集群环境下,有一批积分需要发放给用户,存放在数据库中,实现预算的扣减方法。

//: TODO 可自行定义变量  
/**    
* 扣减预算
* @param consumption 当前需要扣减的消耗量    
* @return 预算的扣减是否成功  
*/
public boolean budgetDeduct(int consumption){
    //: TODO 完成此处的代码
}

思考

作为新手,可能只会想到加锁,比如先加上悲观锁(先来个select for update,锁住之后再来个update 加上超发的where限制等)。所有的事情都在一个服务方法中完成,即可。好处是实现简单,坏处呢,比如响应慢,执行效率低(悲观锁)等。
如果是老手,实现就会比较复杂

表设计

标的物表 target_tbl(id,name,total_number,available_number,froze_number)
抢购交易表trade_order_tbl(order_id,target_tbl_id,state,trade_number,user_id)
target_tbl描述了抢购的目标物件信息:名字,总数量,可用数量,冻结数量。
trade_order_tbl描述了用户执行抢购的交易信息:订单号,抢购标的编号,状态(冻结/失败/成功),交易数量,用户编号。

程序设计(伪码)

public boolean budgetDeduct(int consumption){
    Long tradeOrderId=frozeTarget(userId,targetId,consumption);
    if(tradeOrderId !=null){
        //冻结成功之后,执行业务操作,或是检查
        bool isBusiSucc = doBusiness();
        //业务操作成功,则更新交易,并将抢购商品入个人账
        if(isBusiSucc){
            return updateTrade()
        }else{
            //业务操作失败,则解冻交易
            return undoFrozeTarget();
        }
    }else{
        //冻结不成功,返回失败
        return false;
    }
}
/**
* 需要事务支持,异常回滚
* @param userId 抢购用户编号
* @param targetId 抢购商品编号
* @param consumption 抢购数量
* @return
**/
private Long frozeTarget(String userId,String targetId, int consumption){
    int updated=0;
    //通过乐观锁的方式进行更新,尝试5次,5次仍旧失败,则认为冻结失败
    for(int i=0;i<5;i++){
        //update target_tbl 
        //set froze_number = froze_number+consumption ,available_number = available_number-consumptionwhere available_number>=consumption
        updated = executeFroze();
        if(updated != 0)
        {
            break;
        }else{
            continue;
        }
    }
    if(update == 0){//冻结不成功
        return null;
    }else{
        //冻结成功,添加抢购交易记录
        Long tradeOrderId = createOrder();
        return tradeOrderId;
    }
}
/**
*需要事务支持,异常回滚
*失败需要重试
*/
private bool updateTrade(){
    //1.更新交易表状态为成功
    //2.更新标的表,使标的表的total_number,available_number,froze_number 减去交易表的trade_number
    //3.更新用户信息
}
/**
*需要事务支持,异常回滚
*失败需要重试
*/
private bool undoFrozeTarget(){
    //1.更新交易表状态为失败
    //2.更新标的表,使标的表的available_number+trade_number,froze_number-trade_number
}

另一个思路

一般我们在设计的时候,流水表和商户账户表是同时更新的,比如用户充值,冲完之后,用户账户就是更新后的值。但是在一些业务场景下,流水表和账户表不需要同时更新,账户表通过定时任务来批量更新,或是业务上允许账户变动能容忍一定的滞后。这带了的思考是,先将完整业务流程细分到更新表的操作,再从业务上去梳理,将几个表的操作组合起来,重新组合为业务场景。


沈子平
183 声望17 粉丝

慢慢积累,一切都不会太晚.