V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
glaz
V2EX  ›  程序员

单用户余额高并发支出收入有啥好方案?

  •  
  •   glaz · 1 天前 · 3447 次点击

    比如一个商户一秒一千笔收入记录和一千笔支出记录,咋处理比较好。

    45 条回复    2024-10-15 20:58:38 +08:00
    mooyo
        1
    mooyo  
       1 天前
    合并批量提交?
    yinmin
        2
    yinmin  
       1 天前 via iPhone
    通过消息队列扔给多个微服务子系统并发处理
    kirory
        3
    kirory  
       1 天前
    一个商户一个表,每个记录一行数据
    cooltechbs
        4
    cooltechbs  
       1 天前 via Android
    假如有 N 个虚拟服务节点,每个商户就分 N 行,每个服务实例的请求汇总到其中一行,再加一个定时更新的整体汇总行
    wxf666
        5
    wxf666  
       1 天前
    @glaz 用高性能机子,直接在数据库上操作,可行吗?


    啊哩云的 MySQL 测试[^1]说:

    - 1 核 1G 机器,MySQL 能 6200 读 / 秒,1800 写 / 秒。
    - 16 核 64G 机器,MySQL 能 7.6W 读 / 秒,2.2W 写 / 秒。

    第一种小机子,支持你 2 个类似商户,每秒千次收付款,
    第二种大机子,支持你 22 个。。


    [^1]: https://help.aliyun.com/zh/rds/support/test-results-of-apsaradb-rds-instances-that-run-mysql-8
    xuanbg
        6
    xuanbg  
       1 天前   ❤️ 10
    哪有什么余额记录,从来都只有流水记录。余额都是在需要的时候,从流水中汇总出来的。无论你并发有多高,只要数据库能顶住写入就行。
    ymmud
        7
    ymmud  
       23 小时 58 分钟前
    内存库
    csys
        8
    csys  
       23 小时 55 分钟前 via Android
    如果你的数据库能比较好的做到单行高并发,可以测下能不能满足要求,我记得是有相关数据库方案的

    不过最好还是在应用来做,在应用实现方案这边来解决这个问题,不然业务规模扩大后很难处理,这样的话技术难度会比较高一些
    https://suraciii.github.io/posts/hot-spot-balance-reduce

    类似#1 所说的合并批量提交,拿延迟和失败率换并发吞吐
    把多个交易一起结账落库,用 actor 来控制并发冲突
    csys
        9
    csys  
       23 小时 52 分钟前 via Android
    这里有个简单的数学题,如果你每次处理一个交易需要 1 毫秒,那么在最理想情况下,一个商户 1 秒就只能处理 1000 个交易,实际上远远不到

    所以就不要“每次处理一个交易”
    crysislinux
        10
    crysislinux  
       23 小时 41 分钟前 via Android
    @xuanbg 没余额怎么保证不扣成负的?
    isnullstring
        11
    isnullstring  
       23 小时 38 分钟前
    消息队列

    数据库内做减法
    lcy630409
        12
    lcy630409  
       23 小时 22 分钟前
    前端综合处理 前端给一个时差出来
    比如 等待 转圈圈,1-2s 一般都能忍受
    jimrok
        13
    jimrok  
       23 小时 21 分钟前
    从券商这段的系统设计来看,在报盘的时候就验证余额可用后才报单给交易所,最终账户的日终清算是从中登那边的交割记录和交易所的流水记录一起清算的。所以盘中只是对一个临时账户进行控制,并不是最终账户的数据落地。
    InDom
        14
    InDom  
       23 小时 12 分钟前
    实时余额通过 redis / 内存 储存计算, 初步计算通过的交易记录丢队列. 然后前端等待后端处理结果后再返回.

    后端不间断扫描,每次取走积压的一批处理完毕后任务交还给队列, 前端拿到结果返回.

    哪怕后端每秒扫描一次,也足够用了.
    sujin190
        15
    sujin190  
       23 小时 0 分钟前 via Android   ❤️ 7
    6 楼说的对,这种商户收款付款的打开收支,应该流水优先,一般设计中这种场景都是需要有对账清算的流程的,所以为了提高获取余效率,可以把余额分成已清算余额和未清算余额,已清算的余额可以在对账清算时更新,未清算余额也实时通过流水获取,流水都是不可改的

    关于余额扣负这个问题,单个账号流水很大的,大多数系统都存在未清算金额不可以支出的限制,这个既是技术处理的困难,同时也是你还要过风控不可以立即支出,否则反洗钱啥的法律问题叔叔分分钟找上你

    对账清算可以自动也可以手动,单账户高并发大量流水没有清算对账无论从技术上看还是从法律上看都是不现实的
    qweruiop
        16
    qweruiop  
       22 小时 56 分钟前
    @InDom 高手的意思,需要做减法和检查余额够不够的时候,这个余额都丢 redis ,然后流水记录丢 db ,是这样嘛?
    jorneyr
        17
    jorneyr  
       22 小时 54 分钟前
    架构简单,逻辑简单,更多的硬件支持。

    高并发的事情尽量避免复杂设计。
    yc8332
        18
    yc8332  
       22 小时 51 分钟前
    根本不可能有你说的这种问题。。。高并发最终就是队列处理
    jonsmith
        19
    jonsmith  
       22 小时 45 分钟前 via Android
    瓶颈在哪里?如果是数据库,就上 redis 、消息队列
    InDom
        20
    InDom  
       22 小时 31 分钟前
    @qweruiop #16 不全对, redis 是先过滤明确不合理的请求(可能导致余额为负数的请求), 如果符合条件, 才会进入 db 层的计算, 一批一批的处理,而不是一个一个的处理.

    最终数据还是以 db 为准, 另外, 我也是菜鸡, 这个方案也是我臆想的, 没有实际项目支撑.
    fengYH8080
        21
    fengYH8080  
       22 小时 7 分钟前
    @crysislinux 他这个没有余额的意思是不存储于持久化数据库中,也就不存在频繁的余额变更数据库操作,只有流水的写入操作,在需要的时候通过流水汇总出来余额这个值,就能在内存里根据这个余额去做逻辑限制。
    两种设计方式适用不同性质的系统,我之前就设计过不需要记录余额的系统,处理逻辑会稍微复杂点,好处就是系统中只有流水这一个概念
    snitfk
        22
    snitfk  
       22 小时 0 分钟前
    同一个商户在一秒内这么高的并发?这是给量化服务吗?你们这系统就只服务一个商户?如果商户量增加了,你这架构要处理的量级就马上上升了。
    8355
        23
    8355  
       21 小时 33 分钟前
    不要瞎想,这种业务就是流水插库,你只需要保证执行顺序符合预期就行,使用消息队列进行消费即可。
    收入是流水表字段正数
    首先你要从客观上去理解这个业务,按正常来说是收入大于支出还是支出大于收入,不可能两个一直差不多数量。
    交易类的业务会有一个结算周期,不可能实时结算,包括提现都是有周期的,就是为了减少因为银行/网络延迟等客观原因导致的延迟存在。
    ForMrFang
        24
    ForMrFang  
       21 小时 27 分钟前
    @xuanbg 交易时实时从流水汇总查询余额的话,会不会比较慢.
    wxf666
        25
    wxf666  
       20 小时 52 分钟前
    @fengYH8080 #21 每一笔支出,都需要余额吧?否则咋知道,能否继续花钱呢?


    @sujin190 #15 意思是说,只能花已清算账目后余额内的钱吗?

    如果每天清算一次,清算后还剩 1W 块,第二天可以花无数笔 < 1W 元的支出?

    还是说,23:00 的一笔支出,需要计算( 00:00 ~ 22:59 的余额 + 已清算余额)>= 支出金额,才能花钱?

    按楼主所说,每秒 1000 笔收入 / 支出,那该笔支出,就要算当天 1.66 亿次交易,得出未清算余额???


    sujin190
        26
    sujin190  
       20 小时 10 分钟前
    @wxf666 #25 只能花已经清算的余额 1w ,也就是<=1w 的钱,这个就是账期,1 天其实大多数商户都能接受

    如果你做的是收单那风控和清算是必需的,否则你很可能会面临法律风险
    如果你只是对接支付宝微信支付,这么大流水清算对账也是应该考虑的,虽然风控的事情支付宝微信帮你干了,但是毕竟我们自己的服务器和微信支付宝并不是在一起的,万一程序有点啥未知 bug ,那就有可能直接要破产倒闭了还可能面临债务
    通过清算对账延迟一点对大家都好,商户看到的余额反正是实时的,并不会收这个账期影响,只是并不能立刻支出这部分钱罢了,而且现实中正常交易都是要么小额收入大额支出,要么大额收入小额支出,小额大量收入同时支出的貌似大都不是正常生意哈
    fengYH8080
        27
    fengYH8080  
       20 小时 5 分钟前
    @wxf666 #25 可以简单理解为余额从持久化数据库抽到了内存中,可以解绑掉流水 + 余额的数据库层原子操作。看到这种设计第一印象都会觉得每次汇总一个人的余额非常耗资源,很多处理方式都可以避免的,简单点的可以通过中间表固定时间汇总好之前的余额,汇总实时余额只要这个时间点的余额记录和这个时间点之后的流水做汇总就好了。所以才说这个设计逻辑会复杂点。
    julyclyde
        28
    julyclyde  
       18 小时 57 分钟前
    @crysislinux 确实不能保证不扣成负的
    所以信用卡有所谓超限费(罚款)这么个项目
    guanhui07
        29
    guanhui07  
       18 小时 26 分钟前
    高并发最终就是队列处理 顺序 消费 慢慢排队
    Jackm
        30
    Jackm  
       18 小时 11 分钟前
    收付款用微信支付宝呀,他们有提供接口。像这种级别的给别人交点手续费就交点吧。
    dapang1221
        31
    dapang1221  
       18 小时 2 分钟前
    高并发收入可以理解,但高并发支出……?比如高并发 10 笔的峰值,那这 10 笔其实可以排队在 1 秒内处理完成。但如果说连续 1 小时,每秒里都有上百笔支出……真的有这种场景吗,除了券商高频交易这种
    ivvei
        32
    ivvei  
       15 小时 43 分钟前
    这也不多啊,你要设计啥?
    wxf666
        33
    wxf666  
       15 小时 23 分钟前
    @fengYH8080 #27 《汇总实时余额 = 上次汇总余额记录 + 上次汇总时间点之后的流水累计余额》,

    像上面所说,每天汇总一次的话,23:00 时,当天有 1.66 亿 笔未汇总流水。那计算一次余额的代价,是不是太大了。。



    @sujin190 #26 是第二天内,能花无数笔 <= 1W 的钱吗?还是累计最多 1W 的钱?

    前者不可接受。后者怎么实现呢?每一笔支出,都检查当天流水吗?像上面所说,23:00 时,当天有 1.66 亿 笔流水,这。。

    另外,商户看到的余额,也是(已清算 1W 余额 + 当天 1.66 亿笔流水余额)吗。。这。。
    fengpan567
        34
    fengpan567  
       15 小时 16 分钟前
    kafka 消费
    sujin190
        35
    sujin190  
       15 小时 13 分钟前
    @wxf666 #33 你这个提的就没道理好吧,当天能有 1.66 亿笔流水么!!每天数百亿上千亿的流水?瞎想可不行,真有这么多,清算对账流程还需要比这复杂得多得多,我说的这种能可靠风险低每天处理数百万千万级流水就不错了,想着小学数学解决登月这就不显然扯淡么
    每天几万笔可以用余额加减,这种上亿笔的流水就不可能有简单有可靠又风险低的方案,毕竟就算百万级的异常率,每天损失也高达数百万,亿级别的异常率可不是轻易就能做出来的,不要想着有银弹解决所有问题
    qh666
        36
    qh666  
       15 小时 0 分钟前
    @sujin190 标题说的 一个商户一秒一千笔收入记录和一千笔支出记录 24x60x60x1000x2 不就是 1.7 亿多么
    luckyrayyy
        37
    luckyrayyy  
       14 小时 56 分钟前
    @dapang1221 有的,大主播的退款
    dapang1221
        38
    dapang1221  
       14 小时 47 分钟前
    @luckyrayyy 哪种退款呀,打赏充值还是商品退款。一般退款不是支付,是冲正,用户发起退款,平台接受,平台向支付发退款请求,支付平台给这一单直接标记退款不结算就行了,不涉及主播余额变化
    sujin190
        39
    sujin190  
       14 小时 41 分钟前
    @qh666 #36 虽然如此,但是有实际工程经验的都知道没有这么算的吧,峰值容量 1000 但是能稳定维持这量级 7 、8 小时都已经牛逼顶天了,实际情况也就 3 、4 小时能有这量级,我们设计不是在这空中阁楼的瞎意淫而是需要充分考虑实际工程情况和使用场景的,不考虑实际工程情况和使用场景的设计必定是不靠谱的,再说按你这每天数百亿上千亿的流水,就在这几句话就讨论出可靠的解决方方案这也不现实啊,楼主估计想问的也不是这使用场景吧
    fengYH8080
        40
    fengYH8080  
       14 小时 41 分钟前
    @wxf666 #33 就按你这个说法,你觉得 1.66 亿次的数据库级别的余额变更消耗大点还是一次的汇总消耗大点。再细抠技术细节,到达了这个数量级的系统,已经不是单机能解决的了,需要多机分布式计算。一个人一天都有这么大的量,存储必是要分库分表,汇总时定好时间节点多机后台计算,在具体写入中间汇总表的时候锁一下,把汇总结果和增量未汇总的记录再汇总一下写入内存余额。
    oldking24
        41
    oldking24  
       14 小时 37 分钟前
    好像没有看到大家聊到分布式锁的概念 还是因为并发不需要?求解
    peyppicp
        42
    peyppicp  
       14 小时 33 分钟前
    高并发入账:做汇总入账,落完流水走人,定期汇总刷到账户中
    高并发下账:做子账户拆分,多个子账户出,会有一些 trade off ,需要取舍
    xuanbg
        43
    xuanbg  
       13 小时 0 分钟前
    @crysislinux 有些情况是不需要保证的,负就负了,难道后面就不能冲正了么?
    luckyrayyy
        44
    luckyrayyy  
       12 小时 51 分钟前
    @dapang1221 卖货的商品退款,结算和记账是两个事情吧,不涉及主播已结算余额,但是待结算的钱是要扣除的,就有可能有较大并发量的支出。另外平台补贴户之类的也有可能有大量的支出,不过这个比较灵活一般都可以拆成多个。
    wxf666
        45
    wxf666  
       11 小时 15 分钟前
    @fengYH8080 #40 不是《一次》汇总,是当天《每一笔》都要这么汇总一次,来查余额。。


    @sujin190 #39
    @fengYH8080 #40

    你们觉得,在存储每笔流水时,顺便在这笔流水存当前余额,如何?

    根据 5 楼,啊哩云对 MySQL 的测试,读速大约是写速的 3 ~ 4 倍。

    因此每写一条流水时,额外读一下最新流水记录,取其中余额,加上本次金额组成最新余额,性能损耗应该不大?

    而且,最新流水记录,和即将新生成的流水记录,大概率是临近位置的,应该能利用上 Buffer Pool 里的缓存?所以读损耗进一步减小?



    具体来说,主键设成(用户 ID << 42 | 毫秒时间戳),那么添加一笔支出,SQL 大致如下。

    要知道是否添加成功,可以检查插入了 0 行还是 1 行。前者大概率是余额不够所致。


    ```sql
    INSERT INTO 流水 (流水 ID, 金额, 这笔流水后用户余额, ...)
    SELECT
     (用户 ID << 42 | ${当前毫秒时间戳}),
     ${金额}, -- 支出,应该是负数
     (该用户最新流水记录.这笔流水后用户余额 + ${金额}) AS 该笔支出后余额
    FROM (
      SELECT 这笔流水后用户余额
      FROM 流水
      WHERE 流水 ID BETWEEN (用户 ID << 42) AND (((用户 ID + 1) << 42) - 1)
      ORDER BY 流水 ID DESC
      LIMIT 1
    ) AS 该用户最新流水记录
    WHERE 该笔支出后余额 >= 0
    ```
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3473 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 00:14 · PVG 08:14 · LAX 17:14 · JFK 20:14
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.