• 高性能余额变更方案演化


    背景:
    交易系统账户服务,存在着A与B之间互相转账的需求,如一笔转账交易
    (A+100元,B-100元),A和B其中一方可以为普通用户,也可以为商户,两种角色都可以加款,可以扣款,余额不允许扣成负数。

    方案1:
    每次按账户ID更新余额表。
    为了防止对某账户余额的并发更新,可以采用悲观锁机制,如:

    • 锁定行记录 for update
    • 使用账户Redis分布式锁
    • 使用账户Zookeeper分布式锁
      也可以改进为采用乐观锁机制,如对余额表增加version字段,每次更新时version+1,比如当前版号为5,则sql为:
           update 余额表 set amount = amount + 100 where account_id = xxx and version = 5;
    

    缺点:

    • update并发不高
    • 涉及到A、B转账操作时,假如A是热点账户(商户),则并发冲突急剧增加
      方案2:
      开发童鞋参考了某大型电商库存方案并多次讨论后得出以下架构

    缺点:

    • 热点账户余额查询业务上需忍受非实时
    • 热点账户可能被扣成负数(超扣),也可能会扣不完(少扣)
    • 复杂性提升相当高,开发、运维成本陡增

    方案3:

    查询余额实现:

      select amount from 余额表 where account_id = xxx;
      +
      select sum(amount) from 在途余额表  where is_handle = 0 and account_id = xxx;
    

    可能存在的问题(假设存在以下时序场景):

      select amount from 余额表 where account_id = xxx;
      update 余额表 set amount = amount + 1 where account_id = xxx;
      select sum(amount) from 在途余额表  where is_handle = 0 and account_id = xxx;
    

    即在查询完余额表,在未查询在途额余额表前那一刻,刚好有一次update操作,即数据版本发生了变更,会导致查询出来的余额不准。
    待优化方案:增加版本号,保证余额表和在途余额表是同一个版本

      select amount, version from 余额表 where account_id = xxx;
      +
      select sum(amount) from 在途额余额表 where version >= $version and account_id = xxx;
    

    注:
    1、在途余额表在insert时使用Long.MAX_VALUE
    2、后台定时任务在处理在途额余额表时将当前余额表的version更新到在途余额表的version字段,同时将余额表version+1

    总结: 架构设计不能脱离具体的业务场景,技术架构服务于具体业务。另外即使网上找到的适合大厂的方案,也要根据公司现有开发人力、业务量、运维能力等进行综合考量。

    遗留问题思考: 假如单表insert达到瓶颈,如何伸缩?

    欢迎转载,转载请务必注明出处
  • 相关阅读:
    springboot发送邮件
    事务(进程 ID 64)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品(转)
    Java8中的Stream API基本用法总结
    java时间API,SpringBoot中应用LocalDateTime(日期转换)
    springboot配置自定义消息转换器
    全文检索lucene
    springmvc总结(配置传递参数去除前后空格、参数绑定时处理日期)
    vs2015 安装问题汇总
    浏览器快捷方式被修改的元凶
    使用天平3次,从12个乒乓球找唯一1个轻重未知的废品
  • 原文地址:https://www.cnblogs.com/mzsg/p/11977633.html
Copyright © 2020-2023  润新知