开篇
同一个用户并发扣款时,有一定概率出现数据不一致, 可以使用CAS乐观锁 的方式,在不降低吞吐量,保证数据的一致性:
UPDATE t_yue SET money=$new_money
WHERE uid=$uid AND money=$old_money ;
更详细的描述,详见《 并发扣款,如何保证数据的一致性? 》。
不能采用直接扣减的方式 :
UPDATE t_yue SET money=money-$diff WHERE uid=$uid;
更详细的描述,详见《 并发扣款一致性,幂等性问题 》。
当然,更通用的方式, 可以使用版本号 来实现CAS乐观锁:
UPDATE t_yue SET money=$new_money, ver=$ver_new WHERE uid=$uid AND ver=$ver_old ;
更详细的描述,详见《 并发扣款一致性优化,CAS下ABA问题 》。
对于这个CAS乐观锁方案,很有朋友有疑问: 当并发量高时,版本号比对会导致大量的更新失败,这个方案不适用于高并发场景吗?
今天来聊一聊这个话题。
先分析三个业务场景。
一、QQ
QQ的一些核心业务有:
个人: user( uid , user_info, …)
好友: user_friends( uid , friend_id, …)
加入的群: user_groups( uid , group_id, …)
群: group( gid , group_info, …)
群成员: group_members( gid , uid, …)
个人消息: msgs_user( msg_id , uid, …)
群消息: msgs_group( msg_id , gid, …)
这些信息的读写有一个特点,都会带上uid/gid/msgid属性。
例如, 拉取好友列表 :
select friend_id from user_friends where uid=$uid ;
在用户量很大,并发量很大时, 不同用户/群/消息数据的读写并没有锁冲突 。
画外音: 10W个用户同时读写,彼此没有锁冲突。
只有当,同一个用户,很短的时间内,有大量并发时,才可能存在锁冲突。
画外音:例如, 1个用户,1秒钟读写1W次。
二、微博
微博的核心业务是feed流:
发消息,写操作
刷消息,读操作
微博业务显然是 读多写少 的,在用户刷消息时, 自己feed流里的消息,是由别人发出的 。
查看自己主页feed流 ,最朴素的实现方法是:
(1) 拉取自己关注的用户id_list;
(2) 拉取这些用户最近N条消息;
(3) 将这N*id_list条消息排序;
(4) 返回第一页消息,得到自己主页feed流;
画外音: 这里不展开读扩散,写扩散,详见《读扩散?写扩散?》。
在用户量很大,并发量很大时, 会有一定数据的读写锁冲突 。
画外音: 不像QQ,基本是读写自己的数据,微博要写自己的数据,读别人的数据。
三、12306
12306的核心业务是:
查票,读操作
买票, 写操作
stock(id, num) // 某一列车有多少张余票
在用户量很大,并发量很大时, 有极大的锁冲突 。
画外音: 这个业务,数据量并不大。
这类“秒杀”业务,如果不做特殊的优化, 数据库很容易死锁卡死 ,没有任何人能买票成功。
画外音: 要做什么特殊的优化呢?
收尾
QQ,微博、12306,同样是高并发业务,就数据存储锁冲突来说,各自的难度,数据不一致的概率是不同的。
画外音: 你不能说,QQ不是高并发业务吧。
回到开篇,使用CAS乐观锁进库存扣减:
UPDATE t_yue SET money=$new_money, ver=$ver_new
WHERE uid=$uid AND ver=$ver_old ;
只要有uid这个过滤属性,即使10W用户同时扣款,也不容易出现数据不一致 。
只有当同一个用户,同一秒钟,有大量扣减时,才有一定几率会冲撞,但也不会导致数据不一致。
画外音: 有一位很可爱的水友,说万一PC端和APP端同时下单怎么办。
结论
高并发的扣款场景,可以使用CAS乐观锁,采用select&set方式进行扣款,既能够保证吞吐量,又能够保证一致性。
架构师之路 -分享技术思路
相关文章:
《 并发扣款,如何保证数据的一致性? 》
《 并发扣款一致性优化,CAS下ABA问题 》
《 并发扣款一致性,幂等性问题 》
《 读扩散?写扩散? 》
任何脱离业务的架构设计,都是耍流氓 。