秒杀系统架构分析与实战( 六 )


那么问题来了,假设我们的系统,在5w/s的高并发状态下,平均响应时间从100ms变为250ms(实际情况,甚至更多):
于是,我们的系统剩下了4w的QPS,面对5w每秒的请求,中间相差了1w 。
然后,这才是真正的恶梦开始 。举个例子,高速路口,1秒钟来5部车,每秒通过5部车,高速路口运作正常 。突然,这个路口1秒钟只能通过4部车,车流量仍然依旧,结果必定出现大塞车 。(5条车道忽然变成4条车道的感觉) 。
同理,某一个秒内,20*500个可用连接进程都在满负荷工作中,却仍然有1万个新来请求,没有连接进程可用,系统陷入到异常状态也是预期之内 。

秒杀系统架构分析与实战

文章插图
其实在正常的非高并发的业务场景中,也有类似的情况出现,某个业务请求接口出现问题,响应时间极慢,将整个Web请求响应时间拉得很长,逐渐将Web服务器的可用连接数占满,其他正常的业务请求,无连接进程可用 。
更可怕的问题是,是用户的行为特点,系统越是不可用,用户的点击越频繁,恶性循环最终导致“雪崩”(其中一台Web机器挂了,导致流量分散到其他正常工作的机器上,再导致正常的机器也挂,然后恶性循环),将整个Web系统拖垮 。
##5.3 重启与过载保护## 如果系统发生“雪崩”,贸然重启服务,是无法解决问题的 。最常见的现象是,启动起来后,立刻挂掉 。这个时候,最好在入口层将流量拒绝,然后再将重启 。如果是redis/memcache这种服务也挂了,重启的时候需要注意“预热”,并且很可能需要比较长的时间 。
秒杀和抢购的场景,流量往往是超乎我们系统的准备和想象的 。这个时候,过载保护是必要的 。如果检测到系统满负载状态,拒绝请求也是一种保护措施 。在前端设置过滤是最简单的方式,但是,这种做法是被用户“千夫所指”的行为 。更合适一点的是,将过载保护设置在CGI入口层,快速将客户的直接请求返回 。
#6 作弊的手段:进攻与防守# 秒杀和抢购收到了“海量”的请求,实际上里面的水分是很大的 。不少用户,为了“抢“到商品,会使用“刷票工具”等类型的辅助工具,帮助他们发送尽可能多的请求到服务器 。还有一部分高级用户,制作强大的自动请求脚本 。这种做法的理由也很简单,就是在参与秒杀和抢购的请求中,自己的请求数目占比越多,成功的概率越高 。
这些都是属于“作弊的手段”,不过,有“进攻”就有“防守”,这是一场没有硝烟的战斗哈 。
##6.1 同一个账号,一次性发出多个请求## 部分用户通过浏览器的插件或者其他工具,在秒杀开始的时间里,以自己的账号,一次发送上百甚至更多的请求 。实际上,这样的用户破坏了秒杀和抢购的公平性 。
这种请求在某些没有做数据安全处理的系统里,也可能造成另外一种破坏,导致某些判断条件被绕过 。例如一个简单的领取逻辑,先判断用户是否有参与记录,如果没有则领取成功,最后写入到参与记录中 。这是个非常简单的逻辑,但是,在高并发的场景下,存在深深的漏洞 。多个并发请求通过负载均衡服务器,分配到内网的多台Web服务器,它们首先向存储发送查询请求,然后,在某个请求成功写入参与记录的时间差内,其他的请求获查询到的结果都是“没有参与记录” 。这里,就存在逻辑判断被绕过的风险 。
秒杀系统架构分析与实战

文章插图
应对方案:
在程序入口处,一个账号只允许接受1个请求,其他请求过滤 。不仅解决了同一个账号,发送N个请求的问题,还保证了后续的逻辑流程的安全 。实现方案,可以通过Redis这种内存缓存服务,写入一个标志位(只允许1个请求写成功,结合watch的乐观锁的特性),成功写入的则可以继续参加 。
秒杀系统架构分析与实战

文章插图
或者,自己实现一个服务,将同一个账号的请求放入一个队列中,处理完一个,再处理下一个 。
##6.2 多个账号,一次性发送多个请求## 很多公司的账号注册功能,在发展早期几乎是没有限制的,很容易就可以注册很多个账号 。因此,也导致了出现了一些特殊的工作室,通过编写自动注册脚本,积累了一大批“僵尸账号”,数量庞大,几万甚至几十万的账号不等,专门做各种刷的行为(这就是微博中的“僵尸粉“的来源) 。举个例子,例如微博中有转发抽奖的活动,如果我们使用几万个“僵尸号”去混进去转发,这样就可以大大提升我们中奖的概率 。


推荐阅读