余额,危险的操作,给996留点福报
真的很危险,有人因此进了局子;也有公司因此损失上亿。
想象一下你在一个月黑风高的夜晚,大概是10点多钟的样子,加班归来,打算到小卖部弄盒烟抽。
夜凉风急,你用力裹了下被风鼓起的外套。
那里有你暗恋的收银姑娘。
没日没夜的工作,只有这十几分钟,能让你感到些许生活的意义。
从羞涩的钱包里翻出仅存的一张百元大钞,结账。然后用颤抖的双手接过收银员的找零。
不是因为轻触到了她的指尖。
也并非因她如花的笑靥。
只因为,脑海里竟然不争气的浮现出这样的过程。
balance = dao.getBalance(userid) balance = balance - cost dao.setBalance(userid,balance)
还真是狗改不了吃屎啊,果然还是一个码畜。提醒着自己,自卑的埋下了脸,快步走开。
这是什么?这是我们送给996公司的一点福报。
一波麻6的操作
余额修改,是交易系统里最常见的操作。上面的伪代码,大意是先取出余额,然后扣掉消费,然后再回写余额。通常情况下这不会发生问题。
除非是高并发,与你是否单机无关。
对单一余额的高并发操作自然不是正常人发起的,系统正在承受***,或者自以为是的使用了MQ。在***面前,上面的操作显得不堪一击。
拿一个最严重的例子说明:同时发起了一笔消费20元和消费5元的请求。在经过一波猛如虎的操作之后,两个请求都支付成功了,但只扣除了5元。相当于花了5块钱,买了25的东西。
划重点:把以上操作扩展到提现操作上,就更加的恐怖。比如你发起了一笔100元的提现和0.01元的提现,结果余额被扣减0.01元的提现给覆盖了。这相当于你有了一个提款机,非要薅到平台倒闭为止。
防护办法
通过SQL解决
update user set balance = balance - 20 where userid=id
这条语句就保险了很多,如果考虑到余额不能为负的情况,可以把sql更加精进一点。
update user set balance = balance - 20 where userid=id and balance >= 20
以上sql,就可以保证余额的安全,高并发下的***就变得意义不大了。但会有别的问题,比如重复扣款。
通过锁解决
现实中,这种直接通过sql扣减的应用,规模都比较小。当你的业务逐渐复杂,又没有进行很好的拆分的情况下,先读再设值的情况还是比较普遍的。比如某些营销操作、打折、积分兑换等。
这种情况,可以引入分布式事务。简单点的,只需要使用redis的setnx或者zk来控制就可以;复杂点的方案,可以使用二阶段提交之类的。
分布式锁的业务粒度,要足够粗,才能保护这些余额操作;加锁的粒度,要足够细,才能保证系统的效率。
begin transition(userid)balance = dao.getBalance(userid) balance = balance - cost dao.setBalance(userid,balance)end
类CAS方式解决
java的朋友可以回想下concurrent包的解决方式。那就是引入了CAS,全称Compare And Set。
扩展到分布式环境下,同样可以采用这一策略。即先比较再设值。如果初始值已经变化了,那么不允许set设值。
cas一般通过循环重试的方法进行状态更新,但余额操作一般都是比较单一的,你也可以直接终止操作,并预警风险。
sql类似于:
update user set balance = balance - 20 where userid=id and balance >= 20 and balance = $old_balance
当然,你也可以通过加入版本号概念,而不是余额字段来控制这个过程,但都类似。
变种:版本号
通过在表中加一个额外的字段version,来控制并发。这种方式不去关注余额,可扩展性更强。
version的默认值一般是1,即记录创建时的默认值。
操作的伪代码如下:
version,balance = dao.getBalance(userid) balance = balance - cost dao.exec(" update user set balance = balance - 20 version = version + 1 where userid=id and balance >= 20 and version = $old_version ")
上面的并发***,将会只有一个操作能够成功,我们的余额安全了。
End
赶紧看一下你的余额操作,是否也暴露在风险之下。你可以选择接受福报继续当兄弟,当然也可以将福报还给资本家。
一念成佛,一念成魔。你才是自己的主人。
- 余额,危险的操作,给996留点福报
- 余额,危险的操作,给996留点福报
- Java模拟银行账户简单的存取款、余额查询操作。
- 注册表的危险操作项目,安全配置
- 分析编译器对C关键字的处理『跳过编译器的语法检查,完成很危险的操作』
- nova通过check_instance_lock对危险的操作保护
- 细数linux下的十大危险操作
- 注册表内容+危险操作内容+安全配置
- 中国人的“福报”-996,歪果仁怎么看?
- 细数linux下的十大危险操作
- 注册表一些危险操作
- 梳理注册表的一些危险操作,注册表的安全配置,以及对于注册表的总结
- 数据库防火墙——实现数据库的访问行为控制、危险操作阻断、可疑行为审计
- 电脑操作最危险的18个小动作
- 危险代码:如何使用Unsafe操作内存中的Java类和对象—Part4
- 操作用户余额 Monxin专用(PHP代码函数)
- 前方危险,Photoshop操作的十大误区
- 如何在无审计的环境中追踪Truncate/Drop等危险的DDL操作
- vertica-cascade危险操作
- 996 | 不谈收入谈福报,我们凭什么要喝这碗毒鸡汤!