- 综合
大屏秒杀程序
时间:2010-12-5 17:23:32 作者:百科 来源:百科 查看: 评论:0
内容摘要:双十一那天晚上,我盯着后台的秒杀按钮,手心全是汗。屏幕上的倒计时跳到零,我点下去,页面卡了整整三秒——这三秒里,库存从一万件直接归零。旁边技术小哥喊了一声:“又超卖了。”这不是我第一次写大屏秒杀程序,
双十一那天晚上,大屏我盯着后台的秒杀秒杀按钮,手心全是程序汗。屏幕上的大屏倒计时跳到零,我点下去,秒杀页面卡了整整三秒——这三秒里,程序库存从一万件直接归零。大屏旁边技术小哥喊了一声:“又超卖了。秒杀”这不是程序我第一次写大屏秒杀程序,但每次上线前,大屏心里还是秒杀没底。 秒杀程序的程序核心就一个字:抢。但抢的大屏背后,是秒杀成千上万个请求在同一个瞬间砸向服务器。普通接口每秒能处理几百个请求,程序秒杀场景下,这个数字得翻到几十万。硬件堆再多也没用,瓶颈往往出在数据库上——你让MySQL扛这种流量,它当场就给你脸色看。 我的做法是先把请求拦在缓存层。Redis单机就能扛十万级QPS,比数据库快两个数量级。用户点秒杀按钮,请求先打到Nginx,再转发到应用层,应用层不直接查库,而是去Redis里扣减库存。Redis的`DECR`命令是原子的,天然防超卖。但光有原子操作还不够,你得处理“库存扣成负数”的情况——每次扣减前先查一下剩余量,小于零直接返回“已售罄”。 有人会问,Redis崩了怎么办?这问题问得好。我见过最惨的事故,就是Redis主从切换的瞬间,主节点还没来得及同步数据,从节点顶上来了,库存数据回滚到几秒前——结果超卖了两百件。后来我改用Lua脚本把检查库存和扣减库存合并成一个原子操作,再配合主从哨兵,才算把这个问题压下去。 真正的难点在“公平性”。秒杀拼的是手速,但程序得保证同一用户不能反复刷。我用用户ID做分布式锁,同一时间只允许一个请求进入扣减逻辑。锁的粒度要细——按用户ID加锁,而不是全局锁,否则性能直接腰斩。另外,每个用户限制购买一件,这得靠数据库的唯一索引来兜底,光靠应用层判断不靠谱,总有并发穿透的时候。 大屏上的数字跳得飞快,那是前端每两秒轮询一次订单服务,拉取已售数量。轮询本身压力不大,但别让前端直接查数据库——中间加一层聚合服务,把数据先算好放缓存里,前端只读缓存。我见过有人图省事,让前端直接连订单表,结果秒杀还没结束,数据库连接池就满了,整个系统跟着宕机。 程序上线前,我习惯压测三遍。第一遍看正常流量下的响应时间,第二遍把并发拉到预估峰值的两倍,第三遍故意停掉一个Redis节点,看系统能不能自动切换。压测的时候,监控面板上的曲线像过山车,每次抖动都让我心跳加速。有一次压测发现,Tomcat默认的线程池只有200,直接被流量打穿——调大线程数、改用异步Servlet,才把吞吐量提上去。 秒杀结束,大屏上的数字定格在“10000”,库存恰好清零,没有超卖,也没有少卖。我松了口气,但心里清楚,这套程序下次可能还会出幺蛾子。技术就是这样,你以为堵住了所有漏洞,但总有一个你没想过的边界条件,在某个深夜等着你。