互斥业务在线程或协程实现的困难

m
minquan
楼主 (未名空间)


互斥业务就是有几个业务并发,但是业务之间容易造成干扰,即业务发成了互相否决或潜在互相否决的出错机会

典型的互斥业务在电商体系里就有

例如促销一共卖10件商品
几个顾客在抢,都先检查是否余量还够自己下单
如果够就下单,完成订单

那么可能为A检查还剩5件,它要3件是可以实现的,决定下单
但是很不幸的,在检查和写下这个下单之间,线程控制权被转交给了B下单的过程,B下单就直接扣了3件,则轮到A下单时就白检查了。

所以余量这个变量要被锁。
还有订单号也一样要锁。防止一个以上的单占用同一个订单号。

目前我的开发是在Python的Django里面实现不了这种锁,非得把锁外移到Redis或数据
库里,而且写的很麻烦。

诸位都有很丰富的经验,对于这类问题都怎么搞呢?
m
magliner
2 楼

那你告诉买家,赶紧买,剩的不多了 。 被别人买了,算你倒霉了。 查询,都是有时
效性的,time sensitive, 买家也不好说什么。

我购物都是先放进购物车,我也不知道买不买,等过几天,经常几星期,再checkout, 有时就涨价了,或者没库存了。
和机票一样,几小时就变,你交了钱都未必买的着票, 你得自己确认 直到知道自己座位,才算成交。 其实就算你屁股做到座位了,也可能被人赶下去。

所以我看,这不是个技术问题,是业务逻辑。

m
minquan
3 楼

不是这个,而是几个买家同时下单,然后我的线程去判断可行性,必然先查余量,再写结果;但是多线程不保证这两个动作之间不被别的线程插一杠子,插了可能就白查了,这个错误很严重。

【 在 magliner (magliner) 的大作中提到: 】
: 那你告诉买家,赶紧买,剩的不多了 。 被别人买了,算你倒霉了。 查询,都是有时
: 效性的,time sensitive, 买家也不好说什么。
: 我购物都是先放进购物车,我也不知道买不买,等过几天,经常几星期,再checkout,
: 有时就涨价了,或者没库存了。
: 和机票一样,几小时就变,你交了钱都未必买的着票, 你得自己确认 直到知道自己座
: 位,才算成交。 其实就算你屁股做到座位了,也可能被人赶下去。
: 所以我看,这不是个技术问题,是业务逻辑。

i
insect9
4 楼

你以为电商促销热门商品的时候为啥经常出现事后砍单?

【在 minquan(三民主义)的大作中提到:】
:不是这个,而是几个买家同时下单,然后我的线程去判断可行性,必然先查余量,再写结果;但是多线程不保证这两个动作之间不被别的线程插一杠子,插了可能就白查了,这个错误很严重。


l
longtian
5 楼

先把所有的request都cache到一起,然后按时间计算余量?

【 在 minquan (三民主义) 的大作中提到: 】
: 不是这个,而是几个买家同时下单,然后我的线程去判断可行性,必然先查余量,再写
: 结果;但是多线程不保证这两个动作之间不被别的线程插一杠子,插了可能就白查了,
: 这个错误很严重。
: ,

a
allenx
6 楼

你问问老魏,全中国的火车票业务他都有办法解决,你这个应该是小意思。
e
ekco
7 楼

锁移到数据库未必是坏事,或者logic移到数据库的transaction,再或者不保证下单就买到

m
mitbbsthanks
8 楼

就是加个版本号的事情。更新请求要求行版本号和之前读出的一致,否则放弃更新。

m
minquan
9 楼

所以你认为乐观锁也能保障完全无错?

【 在 mitbbsthanks (ThanksMitBBS) 的大作中提到: 】
: 就是加个版本号的事情。更新请求要求行版本号和之前读出的一致,否则放弃更新。

m
minquan
10 楼

所以你认为乐观锁也能保障完全无错?

【 在 mitbbsthanks (ThanksMitBBS) 的大作中提到: 】
: 就是加个版本号的事情。更新请求要求行版本号和之前读出的一致,否则放弃更新。

c
chebyshev
11 楼

你没有必要保证下单的,一定完美的可以买到。我以前抢过球鞋,没抢到,下单了。可见这是常见的情况。保证这种情况不经常发生即可。

保证下单即可完美买到也可以,交钱买vip会员。既然交了更多的钱,那写写redis的锁也没什么问题。

【 在 minquan(三民主义) 的大作中提到: 】
<br>: 所以你认为乐观锁也能保障完全无错?
<br>

m
minquan
12 楼

他的意思是如果在python线程内实现悲观锁可能死锁,不如放在Redis外部乐观锁,有
时限。

但是他这样做显然多了两次IO

他还认为在Python内加锁严重影响性能

我咋觉得Django多线程框架内,频繁IO切换线程才影响性能呢?

【 在 chebyshev (......) 的大作中提到: 】
: 你没有必要保证下单的,一定完美的可以买到。我以前抢过球鞋,没抢到,下单了。可
: 见这是常见的情况。保证这种情况不经常发生即可。
: 保证下单即可完美买到也可以,交钱买vip会员。既然交了更多的钱,那写写redis的锁
: 也没什么问题。
:
: 所以你认为乐观锁也能保障完全无错?
:

i
ironcool
13 楼

那把查余量和写结果变成一个 task,多个买家发的请求都是不同的 task,丢到 queue 里,然后拿出来一个一个执行。执行这任务的线程不能被阻塞,不然网站瘫掉。
【 在 minquan (三民主义) 的大作中提到: 】
: 不是这个,而是几个买家同时下单,然后我的线程去判断可行性,必然先查余量,再写
: 结果;但是多线程不保证这两个动作之间不被别的线程插一杠子,插了可能就白查了,
: 这个错误很严重。
: ,

d
dumbCoder
14 楼

你说得对, 通常就是要把锁实现在 数据库 或者 Redis 里面.
因为一般的 Web framework 比如 Django, assume 自己 stateless,
所有 stateful 的东西, 最好是存在第三方的管理 states 的地方,
要么是数据库,要么是 memory storage (e.g. Redis)

当然, 你也可以自己起个进程, 实现在这个锁的申请释放.
注意, 本质上这是个分布式锁, 你会有多机多进程来访问,
比如你有10个机器, 都跑着 Django processes,
你必然要有个专门的机器进程, 掌握这个锁的 ground truth.


【 在 minquan (三民主义) 的大作中提到: 】
: 互斥业务就是有几个业务并发,但是业务之间容易造成干扰,即业务发成了互相否决或
: 潜在互相否决的出错机会
: 典型的互斥业务在电商体系里就有
: 例如促销一共卖10件商品
: 几个顾客在抢,都先检查是否余量还够自己下单
: 如果够就下单,完成订单
: 那么可能为A检查还剩5件,它要3件是可以实现的,决定下单
: 但是很不幸的,在检查和写下这个下单之间,线程控制权被转交给了B下单的过程,B下
: 单就直接扣了3件,则轮到A下单时就白检查了。
: 所以余量这个变量要被锁。
: ...................

g
guvest
15 楼

我不知道python 内加复杂锁会不会影响性能。其实你没有说你的网站规模多大。性能
什么的无从谈起。

但我如果我选方案,我会避免python内锁复杂资源。因为不确定性高
。锁放在redis和数据库开发的确定性高。失败的可能性小,像你这个任务,放redis或者数据库锁。
应该是可以一马平川的定好计划和working load走到底没多大挑战性。这样junior 程
序员可以一块一块做。开发总成本也未必高多少。
总之,Avoid uncertainty 是我做项目的第一原则。

两次io没什么问题。包装成卖点,还能再收一次钱。例如加个名字,叫做“
guaranteed prepurchase”。然后说Nike都没这功能。出去卖。

【 在 minquan(三民主义) 的大作中提到: 】
<br>: 他的意思是如果在python线程内实现悲观锁可能死锁,不如放在Redis外
部乐观
锁,有
<br>: 时限。
<br>: 但是他这样做显然多了两次IO
<br>: 他还认为在Python内加锁严重影响性能
<br>: 我咋觉得Django多线程框架内,频繁IO切换线程才影响性能呢?
<br>

g
guvest
16 楼

Python自己管并发状态不确定性很高。分布式锁概念清楚,能实现的程序员工资很高。小庙人家还往往不愿意
来。

【 在 dumbCoder(HumbleCoder 不懂就问-_-) 的大作中提到: 】
<br>: 你说得对, 通常就是要把锁实现在 数据库 或者 Redis 里面.
<br>: 因为一般的 Web framework 比如 Django, assume 自己 stateless,
<br>: 所有 stateful 的东西, 最好是存在第三方的管理 states 的地方,
<br>: 要么是数据库,要么是 memory storage (e.g. Redis)
<br>: 当然, 你也可以自己起个进程, 实现在这个锁的申请释放.
<br>: 注意, 本质上这是个分布式锁, 你会有多机多进程来访问,
<br>: 比如你有10个机器, 都跑着 Django processes,
<br>: 你必然要有个专门的机器进程, 掌握这个锁的 ground truth.
<br>:
<br>

g
guvest
17 楼

Python 那queue 的性能和安全性你测过吗?我反正不敢用。小项目我也不用.我记得以前我用dash,用queue什么的, 画股票动态图,网络延迟的时候,出过毛病。

【 在 ironcool(syscall center) 的大作中提到: 】

: 那把查余量和写结果变成一个 task,多个买家发的请求都是不同的 task,丢到 queue

: 里,然后拿出来一个一个执行。执行这任务的线程不能被阻塞,不然网站瘫掉。

m
minquan
18 楼

你看啊,对于自增的订单号,他这么写

从数据库读一下现在的订单号(单独一个变量)
自己生成一下新订单号,即加一
再让数据库的订单号+1
再读一下数据库里面的订单号,如果和自己生成的不同,就出现了抢占矛盾,先歇一会儿再试

这是什么个意思?

【 在 dumbCoder (HumbleCoder 不懂就问-_-) 的大作中提到: 】
: 你说得对, 通常就是要把锁实现在 数据库 或者 Redis 里面.
: 因为一般的 Web framework 比如 Django, assume 自己 stateless,
: 所有 stateful 的东西, 最好是存在第三方的管理 states 的地方,
: 要么是数据库,要么是 memory storage (e.g. Redis)
: 当然, 你也可以自己起个进程, 实现在这个锁的申请释放.
: 注意, 本质上这是个分布式锁, 你会有多机多进程来访问,
: 比如你有10个机器, 都跑着 Django processes,
: 你必然要有个专门的机器进程, 掌握这个锁的 ground truth.
:

m
minquan
19 楼

你看啊,对于自增的订单号,他这么写

从数据库读一下现在的订单号(单独一个变量)
自己生成一下新订单号,即加一
再让数据库的订单号+1
再读一下数据库里面的订单号,如果和自己生成的不同,就出现了抢占矛盾,先歇一会儿再试

这是什么个意思?

【 在 guvest (我爱你老婆Anna) 的大作中提到: 】

y
yhangw
20 楼

在应用层实现一致性如果能高效且正确,那一定是top 1%的大牛。对我等凡人这就是一个坏主意而已。

m
minquan
21 楼

你的意思还是把锁放在外部如Redis之类更合理?

【 在 yhangw (老妖) 的大作中提到: 】
: 在应用层实现一致性如果能高效且正确,那一定是top 1%的大牛。对我等凡人这就是一
: 个坏主意而已。

m
minquan
22 楼

queue这么烂呐。。。

【 在 guvest (我爱你老婆Anna) 的大作中提到: 】
: Python 那queue 的性能和安全性你测过吗?我反正不敢用。小项目我也不用.我记得以
: 前我用dash,用queue什么的, 画股票动态图,网络延迟的时候,出过毛病。
:
: 那把查余量和写结果变成一个 task,多个买家发的请求都是不同的 task,丢到
: queue
:
: 里,然后拿出来一个一个执行。执行这任务的线程不能被阻塞,不然网站瘫
掉。
:

g
guvest
23 楼

我記得當時是python 3.4吧。現在也許修好了。開源社區強大,進步速度不可低估。我只是說我早先的了解。
另外python內處理類似問題的辦法還有很多五花八門的庫。也未必不可以用。但是找個專家就不容易了。
所以我還是傾向於redis等成熟辦法。

【 在 minquan(三民主义) 的大作中提到: 】

: queue这么烂呐。。。

: 掉。

g
guvest
24 楼

是不是合理,还是要考虑自己的时间和人力预算,还有项目的基本目标吧。不用算那么细致。每个约束上中下三档,分类一下子就出来了。无非就是用成熟但是繁杂琐碎的方案,还是快捷,新,但有一定风险的方案两条路。

【 在 minquan(三民主义) 的大作中提到: 】

: 你的意思还是把锁放在外部如Redis之类更合理?

m
minquan
25 楼

现在的问题是django不返回PG数据库的返回值
本来订单号直接让PG自动增加就可以

------------------------

他被迫这样写,(怕多个同时要增PG内的订单号?)

从数据库读一下现在的订单号(单独一个变量)
自己生成一下新订单号,即加一
再让数据库的订单号+1
再读一下数据库里面的订单号,如果和自己生成的不同,就出现了抢占矛盾,先歇一会 儿再试

------------------------

你看这样写合理不?

【 在 guvest (我爱你老婆Anna) 的大作中提到: 】
: 是不是合理,还是要考虑自己的时间和人力预算,还有项目的基本目标吧。不用算那么
: 细致。每个约束上中下三档,分类一下子就出来了。无非就是用成熟但是繁杂琐碎的方
: 案,还是快捷,新,但有一定风险的方案两条路。
:
: 你的意思还是把锁放在外部如Redis之类更合理?
:

y
yhangw
26 楼

数据库的自增长ID列做主键 这个最成熟没争议吧

scale的话 你就让台机器就只干这件事儿 要能突破单机限制已经是很了不起的应用了

【 在 minquan (三民主义) 的大作中提到: 】
: 现在的问题是django不返回PG数据库的返回值
: 本来订单号直接让PG自动增加就可以
: ------------------------
: 他被迫这样写,(怕多个同时要增PG内的订单号?)
: 从数据库读一下现在的订单号(单独一个变量)
: 自己生成一下新订单号,即加一
: 再让数据库的订单号+1
: 再读一下数据库里面的订单号,如果和自己生成的不同,就出现了抢占矛盾,先歇一会
: 儿再试
: ------------------------
: ...................

m
minquan
27 楼

他现在终于解决了,就是查,增,再返回号,一个交易占用一个号。

【 在 yhangw (老妖) 的大作中提到: 】
: 数据库的自增长ID列做主键 这个最成熟没争议吧
: scale的话 你就让台机器就只干这件事儿 要能突破单机限制已经是很了不起的应用了

r
repast
28 楼

这样还少读一次。这回怎么能返回号了呢?

【 在 minquan (三民主义) 的大作中提到: 】
: 他现在终于解决了,就是查,增,再返回号,一个交易占用一个号。

i
insect9
29 楼

你确定redis的能用?

【在 guvest(我爱你老婆Anna)的大作中提到:】
:我記得當時是python 3.4吧。現在也許修好了。開源社區強大,進步速度不可低估。我只是說我早先的了解。
:另外python內處理類似問題的辦法還有很多五花八門的庫。也未必不可以用。但是找個專家就不容易了。

g
granulae
30 楼

难道你这service就一个instance,所以distributed lock不需要?还是你准备用
python实现一个distributed lock?

【 在 minquan (三民主义) 的大作中提到: 】
: 互斥业务就是有几个业务并发,但是业务之间容易造成干扰,即业务发成了互相否决或
: 潜在互相否决的出错机会
: 典型的互斥业务在电商体系里就有
: 例如促销一共卖10件商品
: 几个顾客在抢,都先检查是否余量还够自己下单
: 如果够就下单,完成订单
: 那么可能为A检查还剩5件,它要3件是可以实现的,决定下单
: 但是很不幸的,在检查和写下这个下单之间,线程控制权被转交给了B下单的过程,B下
: 单就直接扣了3件,则轮到A下单时就白检查了。
: 所以余量这个变量要被锁。
: ...................

m
minquan
31 楼

少读什么?他这个机制和银行排队取号机是一样的。只有一个数据库,所以号不断增,拿到的号不会与别人重复。

【 在 repast (xebec) 的大作中提到: 】
: 这样还少读一次。这回怎么能返回号了呢?

m
minquan
32 楼

只有一个数据库,所以订单号不断增,
拿到的号不会与别的交易重复。

【 在 granulae (努力工作) 的大作中提到: 】
: 难道你这service就一个instance,所以distributed lock不需要?还是你准备用
: python实现一个distributed lock?