小规模抢购系统快速入门

双十一早已变成了消费者固定的节日,去年阿里双11的交易额已达1207亿元,如此卓越的商业成就背后离不开高可用高性能的软硬件系统。本文将针对电商的秒杀和抢购等场景,提高简单有效的解决方案。

基础方案

通常来说,秒杀场景一本会涉及一个静态的HTML页面和一个参与秒杀的Web后台请求接口。静态内容放在CDN服务器或反向代理服务器,后台请求通过负载均衡将请求分发到不同Web服务器,之后通过异步写入的方式减轻数据库压力。(先写入Redis,之后再写入数据库)。

  • 高并发:Web系统通常通过QPS(Query Per Second)衡量其吞吐量,比如一个业务请求的平均响应时间为100ms,如果有10台服务器组成的集群,且每一台服务器的MaxClient为1000个连接,那么系统的理论QPS峰值为10*1000q/0.1s=100000。实际情况下,由于机器处于高负载,CPU在各个线程间的切换非常频繁,平均响应时间会大大增加,如果超过最大承载值,系统性能会迅速下降。
  • 过载保护:当系统真的发生雪崩时,首先要将入口层的流量全部拒绝(推荐在CGI入口层快速响应,降低客户产生不满情绪的可能),然后重启。如果缓存也挂了的话,需要注意“预热”。

数据一致性

在秒杀和抢购的大规模高并发场景下,商品“超发”是一个很正常的现象。假设还剩1件商品,这是系统收到了多个并发请求,都判断商品是否还有库存,这时这些请求都可通过校验,最终导致超发。针对这种情况,通常使用悲观锁、消息队列和乐观锁等方式解决,通常的实践中,乐观锁是最简单有效的。

  • 悲观锁的思路:在修改数据时,采用锁定方式,排斥外部请求,其他请求都需要排队。这种方式虽然解决了数据一致性的问题,但并不适合高并发的场景。因为每个请求都要等待锁,那么当请求很多时,系统的平均响应时间会急剧表达,最终消耗完所有可用连接,导致系统宕机。如果实在是系统环境受限,比如不支持乐观锁的SQLServer版本,在涉及交易时,可以通过UPDLOCK来对更新前的查询操作加锁,如果所有涉及交易信息更新的操作都遵循此原则,那么将不影响查询,通过保证更新前锁定指定记录。

  • 乐观锁思路:这是最为推荐的方案,简单易行,通过version更新(比时间戳更合适),缺点是会增大CPU的开销。对于Mysql,可以采用两个步骤进行,先查询记录,获取制定versionID,之后通过update xxx set xxx=xxx,versionid=oldversionid+1where versionid = oldversionid更新即可。此外,如Redis的watch等功能都支持乐观锁的实现,保证了数据一致性。

  • FIFO队列思路:将请求放入队列,采用FIFO策略,保证了数据一致性,但高并发场景下,队列有可能会被撑爆,即使使用很大的队列,也会造成系统平均响应时间大幅下降。

系统安全

此外,抢购中海量请求有很大的水分,比如为了抢票,用户会使用“刷票工具”等辅助软件,其通过更多请求占得更对的机会,这类场景的常见攻防如下所示。

  • 单一账号多个请求:比如领取奖品,当高并发时就可能出现多次领取的情况。应对方案为一个账号只允许一个请求,可以通过借助Redis的watch乐观锁,通过一个写入标志位解决(在比如10分钟的秒杀活动时间内)。
  • 多个账号多个请求:由于很多网站的账号注册都没有加以限制,因此可以通过自动注册脚本刷出大量“僵尸”账号(比如微博的“僵尸粉”),当网站有活动时,这大量的僵尸账户会影响活动的公平。应对方案包括弹出验证码和直接禁止IP,前者用于判断正式用户,大家也可以看到现在的验证码越来越“花里胡哨”,后者虽然有些粗暴但确实简单高效,风控模块可以通过埋点数据(IP、ClientID设备号),判断出黄牛并加入黑名单。
  • 多个账号不同IP发送请求:大家对木马已经非常熟悉,木马的肉机最常见的用途除了进行DDos攻击外,还可以用于作为随即代理IP的服务,也就是转发IP包。这种场景就非常难判断了,这是就需要设置业务门槛来限制请求,或者通过账号行为的“数据挖掘”来进行清理,之前提到的ClientID也OK。
  • 火车票的抢购:这个是最有意思了,实际上很多的12306代理网站,都是通过搭建一个展示验证码图片的中转服务,通过真人来处理的(有得甚至做成游戏或活动,然用户来帮助抢票)。因为火车票是实名的,解决方案就是黄牛账号抢好票,当整个车次都没票时,大家一般就不会再关注,这是就可以通过退->买的方式,让指定的用户获取到车票。
原文地址:https://www.cnblogs.com/xiong2ge/p/bigpromotion_fast.html