取决于是计费高并发,还是用户高并发。
前者是同一账户,同时并发多个计费请求,导致余额变化,好像一个银行账户,多个银行卡,同时刷卡买东西;后者是多个用户在线,用各自的账户消费,互不影响。
看题设,更像是前者,就基于计费高并发来讨论。
曾遇到过一个需求,每个账户每秒有几百次计费请求,要求很简单,
1,又快又准。
2,余额不为负。
同一个字段被并发修改,很自然会想到用lock,但系统有很多其他业务逻辑,计费只是很小的一部分,要足够的轻,就开始考虑尽量无锁的方案。大概思路如下,
记录持久化,余额内存化。
余额是充值和消费的结果,在不断变化,但充值和消费是记录,一旦发生,不会再变,某时某刻花了10块,这条记录产生了,就永远不会变。这类记录持久化,放在DB里。
有了记录,可以在任何时刻,重建余额。这个余额是否需要持久化,不一定,还要考虑是否存在过期等。我们虽选择持久化余额,但不加锁,因为读写不发生在DB上,而是在内存里。
内存里,用户有两个值,一个是余额,一个是花费。用户消费时,余额不变,花费增加。两个问题,
为什不直接减余额呢?
不改余额,就可以保证内存里的余额始终和DB中的一致,而内存里花费始终和消费记录一致。用户的实时余额 = 余额 - 花费。
内存计费是否加锁?
余额不为负,意味着要先确认实时余额 > 所需花费,才能消费,check, then update,这并不是atomic 的,意味着存在 race condition,计费函数是不是一定要加锁呢?
如果先查余额,再扣钱,的确要加锁;但也可以先扣钱,再查余额,若小于0,则把钱加回来,返回计费失败,阻止消费,这样就不用加锁了。当然,余额和花费应选Atomic数据类型。
这样高并发下的余额扣减就变得非常的轻,对 performance 几乎没有影响,也满足了又快又准的需求。