美文网首页
账户余额更新优化

账户余额更新优化

作者: little多米 | 来源:发表于2022-04-06 19:32 被阅读0次

    业务场景
    用户预存一定余额,可以用余额在平台购买套餐商品,支付扣除余额需控制并发,当前采用的是乐观锁方式。即每个用户的余额记录都有一个版本号,更新记录时,需要带上版本号。版本号采用整数递增。
    问题
    当有两个扣减余额的操作同时发生时,其中一个有几率失败。失败结果直接返回给用户,此时用户操作重试即可,但会影响用户体验。如果一直处于高并发状态,用户可能会连续操作失败多次。主要针对此扣款失败场景进行优化。
    方案演进
    增加失败重试

    int i = 0, max = 3;//最多尝试3次
    while (i < max && !success) {
        //获取余额记录
        AgentRechargeEntity arEntity = agentRechargeService.findByAgentId(context.getAdminUserEntity().getAgentId());
        //版本记录值,用于控制并发操作
        Integer exceptTxVersion = arEntity.getTxVersion();
        //修改金额计算
        //更新余额
        success = agentRechargeService.updateMoneyByExpectTxVersion(id, exceptTxVersion, money);
        i++;
    }
    

    进行上面重试修改之后,仍然存在失败日志


    image.png

    通过分析日志可知,失败时确实有三次重试,说明我们修改的代码是生效的。问题在于,失败后重新获取的记录值仍然是老的数据,版本号expectTxVersion没有变化。实际获取上次更新记录值如下。


    image.png

    怀疑是可能存在缓存,该方法使用的是mybatis框架,由于我们没有人为增加缓存,会不会是mybatis的缓存。经研究,mybatis默认是开启二级缓存的,于是通过在select方法上增加flushCache="true" useCache="false"配置去除缓存。


    image.png

    然后更新上线了,本以为就此结束,然而。。。还是一样的失败日志。
    重新分析:更新失败说明版本号已经变更了,意味着其他修改已经提交入库了。
    为什么没有读到其他事务的最新数据呢,研究一下事务的隔离级别。
    查看mysql默认的隔离级别:

    select @@transaction_isolation;
    
    image.png

    默认为:可重复读,看下该级别的定义。

    一个事务启动的时候,能够看到所有已经提交的事务结果。但是之后,这个事务执行期间,其他事务的更新对它不可见。

    因为获取记录操作是在事务中,所以重复获取不能得到最新数据。
    因此,可以将数据获取排除到事务之外,主要用spring的事务传递管理,设置为Propagation.NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

    image.png

    再看日志,虽然也有失败,但基本重试一次之后就成功。


    image.png

    至此,问题解决。
    总结
    本以为是一个简单的重试优化,逐渐引出mybatis二级缓存和数据库的事务管理。任何一个点的遗漏都达不到想要的效果。平时的知识储备是必要的,否则遇到问题时将花费成倍的时间。

    相关文章

      网友评论

          本文标题:账户余额更新优化

          本文链接:https://www.haomeiwen.com/subject/jxkdsrtx.html