关于分布式系统中的并发问题

2020-01-16 16:11:35 +08:00
 Renco

问下各位,在分布式系统中,我的模块中的接口属于被调用的,用户的操作不会直接涉及我的模块,一般由其他模块调用我。这种时候我要如何考虑我模块中的并发问题。

我现在负责的会员模板有一个余额消费的功能,由订单模块,调用我会员消费,我只需要将订单模块传给我的消费金额和相关会员账号进行 扣款处理即可,项目经理让我排查一下其中出现的并发场景。但是我不太清楚分布式系统中的,并发问题是怎么产生的。

6943 次点击
所在节点    Java
40 条回复
CoderGeek
2020-01-16 21:58:20 +08:00
看你描述已经流转到你内部系统了 不要跑偏了
CoderGeek
2020-01-16 22:03:49 +08:00
对了哦我说这些是单个用户才需要考虑的哦,多个用户间不互转不存在并发问题
wccc
2020-01-16 23:40:49 +08:00
加版本号即可,保持接口的幂等
wysnylc
2020-01-17 03:30:39 +08:00
分布式锁性能差,做多队列单消费者
保证一个用户任务在一个队列中
上面要加锁的一律不看,并发的终极方案就是队列
队列分组就 hash 取余,后期可升级一致性哈希环
btnokami
2020-01-17 07:04:27 +08:00
所以你的模块只是作为 Proxy 来处理 request 发给 database,还是 databse 那一层呢。
感觉把并发问题推给 database layer 然后用不同的 isolation level 来处理会比较简单
btnokami
2020-01-17 07:06:51 +08:00
分布式计算就两个终极问题,exactly once delivery 和 consensus。。。往上套就好了
lewis89
2020-01-17 07:30:12 +08:00
@btnokami #26 非常赞同..
lewis89
2020-01-17 07:32:34 +08:00
@wysnylc #24 很显然 楼主的场景跟楼主公司的业务量并没有那么大的规模,如果有的话 肯定不会轮到写代码的来排查这个问题了,在架构设计上就要避免了..
lewis89
2020-01-17 07:39:11 +08:00
@wysnylc #24 我看它的情况,会员系统应该本身应该就维护了 扣款 跟 会员权限配置 的事务操作,这本身就是一个本地事务,如果别人调你的话,你只要对外保证幂等性即可,一般分布式调用链会有 SpanId 调用 用来维护调用链的(像 SpringCloud-Seluth ),最好对 SpanId 做一个幂等性,有些中间件会在超时的时候做一些重试操作,很有可能单个订单会调用两次扣款操作
lewis89
2020-01-17 07:41:49 +08:00
或者可以针对订单 ID 做幂等性校验 把订单 ID 本地落库跟会员扣款放到同一个本地事务 commit 进数据库,做好隔离级别校验跟订单 ID 唯一性校验,如果有重复订单 ID 落库 直接吞掉异常 正常返回即可
passerbytiny
2020-01-17 09:27:27 +08:00
前方在极短的时间内连续调用你的接口,这个时候你的接口,可能是多节点同时执行,也可能是单节点多线程同时执行,但访问的数据库只有一个:这就是并发场景。这种场景,单节点应用和分布式应用都会出现。

这个场景你如果不考虑,那么可能出现的问题是:一,没上事务,余额表的变动历史将乱成一锅粥;二,上了事务,但在扣余额的时候没做二次验证,余额会被口称负的。

解决方法如下,顺序是从简单到复杂,所有方法都要先有事务(单节点事务):
一、乐观锁,只适合单节点应用。这个太常见了,而且你也不是单节点应用,就不细说了。
二、实时动态扣费,适合订单模块(不是你的模块)并发量不是特别高的情况。你在扣费的时候不直接扣,先去查下当前余额,如果扣完不为负,再扣费。你的接口的返回结果不是 void,也不是 true/false,而是一个对象,包含这些字段:是否扣款成功、扣费前的实际余额、扣费后的实际余额。
三、最终一致性,适合订单模块超高并发量的情况。直接扣费,返回结果总是成功,如果扣成负的了,发事件通知,让其他模块去做补偿(或者啥也不干就允许余额为负,或者再通知订单模块去取消订单)。

最后说一句,看见分布式事务或者分布式锁的回复,请直接忽略,这是老早就被淘汰的技术。
passerbytiny
2020-01-17 09:38:01 +08:00
还有各种回复“幂等”的,你们在想什么。首先,这是接口调用不是事件订阅,没有重发和无序性,无需考虑幂等;其次,扣余额这种动作你做幂等? 10 天前扣 1 元跟今天扣 1 元的结果一摸一样?
ke1e
2020-01-17 09:51:37 +08:00
最简单的就是模拟并发情况,看会出现什么问题,然后再去解决
xuanbg
2020-01-17 10:08:30 +08:00
楼主你只需要考虑你的模块有多个服务实例的情况下,每个实例都在同一个时间扣同一个用户的钱的时候,如何保证扣款正确。
bukeshuo
2020-01-17 10:39:41 +08:00
数据库乐观锁 或者 直接 分布式锁 锁账号
Raymon111111
2020-01-17 10:42:41 +08:00
。。。

楼里很多人是不是根本没有做过业务,只看过书,一堆什么分布式锁都出来了

这是要梳理场景,不是解决方案。何况场景没有,解决方案是怎么意淫出来的

并发问题主要考虑两点,这个接口短时间相同请求发起两次请求,这个接口被不同请求同时调用
leafre
2020-01-17 11:15:17 +08:00
@passerbytiny 好吧 我们最近上线的项目就是使用被淘汰的分布式锁
peyppicp
2020-01-17 11:29:47 +08:00
修改为订单模式,每笔余额扣减都有一个固定单号,需要明确一个状态机,保证状态流转正确

做业务时,开启事务,select for update 锁住对应的单号,用户的余额,然后在本事务中做业务,完事了 commit

这种同一单号的并发请求过来,一定会被挂起,执行代码中增加判断订单状态的逻辑就好了,如果本单状态为成功则幂等返回成功。
peyppicp
2020-01-17 11:31:26 +08:00
这个感觉压根没必要上到分布式系统的层面,也没有必要考虑分布式锁,用 db 抗住就完事了。

你这是有热点会员吗,同一个会员做大量余额扣减?如果是这样完全可以考虑用子会员的方法实现,也不差的
wc951
2020-01-17 12:03:32 +08:00
扣款表里把订单号加唯一索引

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/638475

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX