zoukankan      html  css  js  c++  java
  • 高性能余额变更方案演化

    背景:
    交易系统账户服务,存在着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达到瓶颈,如何伸缩?

    欢迎转载,转载请务必注明出处
  • 相关阅读:
    ubuntu 安装 redis desktop manager
    ubuntu 升级内核
    Ubuntu 内核升级,导致无法正常启动
    spring mvc 上传文件,但是接收到文件后发现文件变大,且文件打不开(multipartfile)
    angular5 open modal
    POJ 1426 Find the Multiple(二维DP)
    POJ 3093 Margritas
    POJ 3260 The Fewest Coins
    POJ 1837 Balance(二维DP)
    POJ 1337 A Lazy Worker
  • 原文地址:https://www.cnblogs.com/mzsg/p/11977633.html
Copyright © 2011-2022 走看看