阿里月饼事件

阿里月饼事件即将进入尾声,网上的讨论集中在阿里对待员工的措施是否有错,以及员工使用脚本刷月饼这件事是否有错。我就不参合这个讨论了,换一个角度讨论一下。 tl;dr: 我觉得整件事情,锅应该由抢购系统的后端开发来背。阿里团队应该将抢购系统的后端开发本月的代码质量一览评审评为 1/5。结案。

抢购系统的后端同学没有很好地完成该系统的开发,这是显而易见的事实:每人应设定只能下 3 个订单,每个订单上限是 3 个月饼。然而,有用户在正常使用系统的情况下,下了上百个订单。

首先,后端同学应该做好防御式编程,然而他们没有。限购这个需求是抢购系统的核心问题之一,但是后端同学没有解决问题,只写出了一个没有限购限制的勉强可用的抢购系统。如果后端同学完成了这一点,上面提到的评审分数可以在 3/5 或者 4/5.

先说防御式编程。抢购的本质是计算机领域的一个经典命题:临界资源争抢。如果后端同学是科班毕业,并且大学没有混吃混喝;或者后端同学是非科班毕业,业余对操作系统和数据库设计有足够的兴趣,他们应该知道,临界资源的争抢,一个经典解决方案是加锁。

相信阿里内网抢购系统的源码是这样子的:

收到请求,为请求的用户下一个订单。

一个野生程序员都能写到这个程度(野生程序员的写法依然是错的,但是阿里内网抢购系统的实现比野生程序员的实现还烂):

收到请求,如果请求用户下了超过3订单或历史购买加本次购买超过9个,驳回请求,否则为请求的用户下一个订单。

上面的实现无法正确处理并发场景。

在考虑到抢购对象是内网用户,大约在10万用户级别,我们可以不用宰牛刀(分布式数据库),直接使用关系型数据库(例如 MySQL),依赖关系统数据库提供的 ACID 特性完成一个可靠的事务。在确认合适的数据库隔离级别以后,我们可以使用乐观锁完成并发请求的回滚。设计数据库表结构:

抢购:
    用户ID
    购买数量
    用户购买序列(数字)
    约束:用户ID+用户购买序列的组合是唯一的

数据库事务是:

收到请求,如果请求用户下了超过3订单或历史购买加本次购买超过9个,驳回请求,否则(尝试为请求的用户下一个订单,如果数据库已经存在同样的[用户ID, 购买序列], 则回滚数据库,驳回请求)。

现在的逻辑差不多可以是正确的了。流程图可以见 Martin Fowler 的研究:http://martinfowler.com/eaaCatalog/optimisticOfflineLock.html

当然,上面的实现不是唯一的,我们还有其它的方式来实现这个需求。

其次,后端同学本可以用 Rate Limit 等方案对抢购请求进行削峰,但是他们没有做。当然,这是可选项,在满足第一点的情况下,Rate Limit 只是提升系统的性能,对抢购的正确性没有影响。如果完成了这一项,上面提到的评审分数可以在 4/5 或者 5/5.

最后,被开除的4位,在写代码时,有坏味道,没有考虑边界。这个不多说了,很多人都能看到这个问题。我想说的是,防御式编程不仅在后端开发那边需要做,前端开发这边也需要做。在网络世界,我们只能通过请求和响应这种方式来通信。而请求和响应是可以造假,甚至被劫持篡改(非HTTPS场景)。我们在遵照合约编写代码时,要时刻注意:

  • 不要相信HR
  • 不要相信PM
  • 不要相信后端
  • 不要相信前端
  • 不要相信一个月前的自己

以上可能是本次事件的少有的 PMA 。

EOF