桂林网站建站,深圳做网站个人,做跨境电商的网站,检查网站是否做301万事皆有因 这段似乎都成我写blog标准开头。言归正转#xff0c;公司以前业务涉及到秒杀#xff0c;并且是白天从10点起到晚上10点每小时一次#xff08;TT天天心惊肉跳的#xff09;#xff0c;周六还有个大礼包活动#xff08;重量级#xff0c;经常会出一些你意想不到…万事皆有因 这段似乎都成我写blog标准开头。言归正转公司以前业务涉及到秒杀并且是白天从10点起到晚上10点每小时一次TT天天心惊肉跳的周六还有个大礼包活动重量级经常会出一些你意想不到的事情例如不活跃的用户突然间活跃了量级飙升TT。同时最近随着创业的兴起还是有很多人关注秒杀这技术怎么做。虽然很多NB的大厂小米淘宝JD等已经讲过这东西了但是我还是想讲讲这件事情。下面我就说说一个小厂是如何做秒杀的。 小厂有多小小厂有多大 后端只有2个研发工程师和2个前端工程师当时还没有全职的运维不过服务器的数量有40多台还是挺多的。用户量呢下载和注册都在千万级别了活跃也在百万级别。好了小厂很小但是小厂也很大。 初出茅庐 很多人感觉敢用初出茅庐这标题应该很牛吧然而并没有。并且是意想不到的惨惨不忍睹。第一个版本的秒杀系统完全是依赖MySQL的事务不言而喻大家都会知道有多惨。我直接告诉大家结果就可以了 整个系统在秒杀期间基本上停摆了500和超时异常的多。 准备秒杀的产品数量是100最后卖出去了400份。 我们来分析下为什么会这样 MySQL本身能承载链接数量有限在秒杀的时候大量的链接处在事务状态且绝大部分事务是需要回滚的这就造成了很大的IO压力和计算压力 那为什么会超卖呢因为最开始使用的主从结构读写是分离的主库压力那么大从库同步跟不上造成了卖出去的产品在毫秒级内再查询结果看起来就是没卖出去。简而言之就是就是技术不熟悉导致设计失误。 初窥门径 出第一次事故的时候说句心里话对一个刚毕业1年的工程师还是挺蒙然后就各种猜想。不过好在当时淘宝的一个人的blog上提了MySQL句事务的问题算是找到方向了。然后就这样秒杀活动就先暂停了一个星期这个星期中我和同事都做了什么呢 搭建了一个测试环境模拟了下秒杀的情况观察了MySQL的事务和主从的整体情况 修改秒杀流程 我先说下第一版的流程 从用户数据库查询用户积分是否充足从规则数据库中查询用户是否符合条件 从数据库中读出一个产品的ID 然后事务性的将产品ID和用户ID关联减少用户积分和更新用户规则数据更新产品ID的状态 那么问题就明显了读产品ID的时候是没有事务的这必然会存在问题的。那么我们是如何修改的呢将读取产品ID这件事放入了整个事务中。那么整个流程就变成了 从用户数据库查询用户积分是否充足从规则数据库中查询用户是否符合条件 事务性的读出符合条件的产品并立刻更新状态接着完成用户ID和产品ID的关联及减少积分等工作 那这样还有问题吗依然有最后还是超卖了大家会问为什么这里面我们犯了另一个错误使用代码判断产品的状态而非存储过程这样即便是在数据库事务内但没有可以触发数据库事务回滚的条件所以还会错误的将卖出的产品再次更新为卖出的状态。经历两次惨痛的教训我们才逐步的走上正轨一个地方不会跌倒三次。 登堂入室 我们已经发现了很多问题最后该怎么解决我们决定先解决正确性再解决速度的问题我们使用了一段时间的存储过程加关键ID做成唯一主键的方式整个秒杀流程的第二部分就是个完整的存储过程往事不堪回首天天被用户骂非常慢。这个时候唯一能做的就是补充理论知识发奋图强了。 在这个第二个版本的设计中我们开始采用Redis我们测试了Redis的pubsub机制最开始想使用Redis的pubsub进行排队现在想想有点幼稚但是老天帮了我一把当时鬼使神差的就感觉这机制不靠谱。但是最终的方案嗯使用了正向队列。何为正向队列我们将产品的ID在秒杀开始前全部读入指定的队列中秒杀流程就变成了 判读Redis队列是否为0为0结束 判读用户是否符合规则是否有足够多的积分 从队列pop出一个产品ID如果pop不出来就结束 开事务改变产品ID的状态关联用户ID和产品ID更新规则和积分 这个时候基本上彻底解决了超卖和性能的问题了但是还会有用户在骂为什么因为还不够快。 渐入佳境 我们发现为什么会慢因为数据库的事务回滚虽然少了但是还是处理不过来1s也就那100多个事务能完成剩下的各种跟不上。此时此刻我们直接采购了当时算是比较强劲的数据库服务器事务量一下提高到了1000tps。但是这远远跟不上用户的增长速度TT没业务也哭有业务也哭。 我们既然已经发现了排队理论这么有用我们决定使用RabbitMQ延迟处理队列。经过这次改造我们秒杀的流程就变成了 判断Redis队列是否为0为0结束 判读用户是否符合规则是否有足够积分 从队列pop出一个产品ID如果pop不出来就结束 将用户ID和产品ID放入RabbitMQ中后面的消费者慢慢的吞下去 这时候用户在速度上算是基本满意了不过却带来了新的问题。判断用户是否符合规则的时候由于消费者慢慢的消化而数据库没有实时的更新导致一个用户可以秒杀多个商品很多用户就不满意了TT用户是上帝。 略有小成 我们再次拿出了强大的Redis我们将Redis当作缓存。我们把秒杀的业务逻辑直接变成了这样 先判断Redis的队列是否为0为0结束 判断Redis中用户的信息是否符合规则积分是否符合规则 从队列pop出一个产品ID如果pop不出来就立刻结束 立刻更新Redis中用户的缓存信息和积分信息再放入RabbitMQ让消费者消费 这样看起起来似乎没什么问题了但是还是存在问题的就是pop出产品ID到更新Redis用户信息的一瞬间还是能让部分用户钻空子的毕竟Redis没有MySQL那种强事务机制。 心领神会 在这个阶段我们用Erlang的mnesia写了一个Redis特定功能替代品但使用了段时间很快放弃了因为我们找到了更好的解决方式。让RabbitMQ的消费者使用一致性的hash那么特定的用户一定会落到特定的消费者身上消费者做去重判断。这样减少了我们自己维护基础软件的成本2个后端工程师TT别瞎折腾。 随心所欲 当我们的用户量逐步上升系统依然出现吃紧和性能跟不上的阶段。 这个时候我们大量使用一致性Hash和随机算法其中过程就变成了。 将秒杀的产品ID分成多个队列放在Redis集群上然后将一个产品总数量放在一个Redis上这个Redis是瓶颈但是基本上20W的TPS满满的达到了 为用户随机一个数字在一定范围内直接告诉秒杀失败纯看运气纯丢给应用服务器去玩了 检查用户规则和用户积分还有产品总数量总数量为0直接结束。 为用户随机一个产品ID队列尝试poppop不出数据直接结束还是看运气 更新用户Redis的缓存和产品总数量的缓存decr然后交给RabbitMQ和消费者慢慢处理。 这个时候基本上30wTPS随便玩。 返璞归真 说了这么多废话总结下吧。对于秒杀这种业务优先保稳定和正确最后才能保服务量。不稳定没得玩不正确很可能一单亏死。技术上我个人认为小厂也能做看似很NB的秒杀只要用好以下几个相关技术 削峰不管是随机丢弃还是多层筛选尽可能减少进入核心业务的用户数 排队在秒杀场景下排队不单单可以减少系统压力还能保证正确性 分区使用分区可以降低一个节点当机带来整体性的损害或者雪崩性的系统不可用 最终一致很多时候不一定要强一致性只要能保证最后数据的正确哪怕是手工修复都能带来大规模的性能提升 转载于:https://www.cnblogs.com/liuchuanfeng/p/6908365.html