因为明天要面的公司要求看起来像是做商城业务的。所以今晚赶紧啃一下高并发和秒杀等常见业务。其实工作经验中虽然有类似商城的项目,但是并没有很注意高并发(当时是外包业务,甲方有自己的团队,所以我们基本上只给实现了功能)。所以我这里用的都是redis。然后再声明一下,我做的都是demo!最简版。理论上是可以实现秒杀的。但是真正跑起来还要测试。
首先说一下术语的介绍:
消息队列
其实所谓的消息队列,仔细分析名字也不是很难理解。就是把需要处理的消息都排成队列处理。用一个实际情况来理解:有时候我们去买火车票(春运期间的人流量),如果大家没有规则都在售票窗口围着,就可能出现各种情况,谁先来的没先买到,谁挤谁了,谁踩谁脚了,谁和谁打起来了,谁把售票的小姐姐打死了,完了,大家都买不了票了,而且还容易一起进局子!所以这种情况下我们要排队。
同样消息队列也差不多是这样的。有时候访问的多了,一个服务器处理不过来了,就一个个先排着队处理。这个其实有个前提就是访问的要愿意等一会儿。不过电脑的反应又不是售票,要那么久,可能几十几百个访问需要的速度也就几秒钟,大多数时候用户是不注意的,而且我们还很鸡贼的给个假反应。比如说秒杀的时候,人家就截取前一千个,其实你点抢的时候就已经注定没你份了,但是因为电脑没算出来,所以这个时候页面告诉你,抢购中~~结果抢购了几秒钟再给你刷个页面,商品抢完了。这种情况大家应该多多少少遇到了,虽然具体的实现是不一样的,但是操作就是类似于消息队列的操作。
我觉得这么说完起家应该对消息队列有一个明确的理解和认识了。我这个题目是秒杀,所以应该都是用消息队列实现的。
消息队列的类别
就跟代码可以用c,java,php等等,消息队列也有多种实现方式。但是因为我们用到消息队列本身就是为了解决高并发,所以用数据库做消息队列太傻了,一般都是使用中间件。
这里简单说一下官方的消息队列的使用场景:当不需要立即获得结果,但是并发量又需要进行控制的时候,差不多就是需要使用消息队列的时候。
然后消息队列的种类很多:当前使用较多的消息队列有RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMq等,而部分数据库如Redis、Mysql以及phxsql也可实现消息队列的功能。
然后我其实并没有专门使用过消息队列,都是使用redis临时做做秒杀,所以真正的mq中间件也没实际上使用过,最多就是看看文档做做demo,而且现在只接触过ActiveMQ。不过我看了好多介绍,所以在这里也共享一下各个mq的性能比较(我也是找个各种数据贴,最下面会附上参考文章,我不会做成表格,所以对付看吧):
ActiveMQ:java语言开发的,单机吞吐量:万级,时效性:ms级,可用性:高(主从架构)
功能特性:成熟的产品,在很多公司得到应用;有较多的文档;各种协议支持较好
RabbitMQ:erlang语言开发的,单机吞吐量:万级,时效性:us级,可用性:高(主从架构),功能特性:基于erlang开发,所以并发能力很强,性能极其好,延时很低;管理界面较丰富
RocketMQ:java语言开发的,单机吞吐量:10万级,时效性:ms级,可用性:非常高(分布式架构),功能特性:MQ功能比较完备,扩展性佳
kafka:scala语言开发的,单机吞吐量:10万级,时效性:ms级以内,可用性:非常高(分布式架构),功能特性:只支持主要的MQ功能,像一些消息查询,消息回溯等功能没有提供,毕竟是为大数据准备的,在大数据领域应用广。
综合上面的材料得出以下两点:
(1)中小型软件公司,建议选RabbitMQ.一方面,erlang语言天生具备高并发的特性,而且他的管理界面用起来十分方便。正所谓,成也萧何,败也萧何!他的弊端也在这里,虽然RabbitMQ是开源的,然而国内有几个能定制化开发erlang的程序员呢?所幸,RabbitMQ的社区十分活跃,可以解决开发过程中遇到的bug,这点对于中小型公司来说十分重要。不考虑rocketmq和kafka的原因是,一方面中小型软件公司不如互联网公司,数据量没那么大,选消息中间件,应首选功能比较完备的,所以kafka排除。不考虑rocketmq的原因是,rocketmq是阿里出品,如果阿里放弃维护rocketmq,中小型公司一般抽不出人来进行rocketmq的定制化开发,因此不推荐。
(2)大型软件公司,根据具体使用在rocketMq和kafka之间二选一。一方面,大型软件公司,具备足够的资金搭建分布式环境,也具备足够大的数据量。针对rocketMQ,大型软件公司也可以抽出人手对rocketMQ进行定制化开发,毕竟国内有能力改JAVA源码的人,还是相当多的。至于kafka,根据业务场景选择,如果有日志采集功能,肯定是首选kafka了。具体该选哪个,看使用场景。
然后因为我没用过,所以这里就是引用大佬的解释,刚刚也说了,上面这这高大上的东西我都不知道,咱们这里的秒杀用redis来做消息队列。
消息队列的作用和优缺点
这里说一下消息队列的作用,可能说的不全,但是是我目前查到的和想到的全部了:
- 解耦
其实这个我也经验不多,然后我根据自己的理解举个例子吧(说错勿喷,然后有不同意见欢迎指点,谢谢):我们登录的token验证。token很少存在数据库里,而是存在容器或者缓存中,这样在验证的时候只需要去中间件中查询时候登录,而不是每一次都到数据库中查询token是否正确。这样我们在网关处就可以验证token了。并且网关可以独立验证,不用连数据库。
然后我觉得这个消息队列的作用之一解耦是一定在分布式中才有作用的,像是传统的单服务,消息队列是起不到解耦作用的。我也不确定这个想法对不对,但是我看到的文件都是解除服务之间的耦合度,单服务根本没有啊!(ps:感谢朋友留言,之前我这里说的很不严谨。解耦是一种思想,单服务和微服务都有。但是消息队列只在微服务中能起到解耦作用)。 - 异步
这个就比较好理解了,就跟我刚刚说的排队似的,一个个排队来。不然没个规矩肯定要乱套啊。假如这个人都说完要买的票了,出票了结果另一个人挤上来了,这张票给后来的人了,那不是出乱子了么?其实就可以理解为一个简单的异步锁。 - 削峰
这个我刚入行的老板就给我讲过这个问题,一个程序,平时一天几十个访问量。但是有时候有秒杀活动,一秒上千访问量。如果说加服务器吧,还不值得,因为平时用不到。不加服务器这个秒杀一跑就瘫,这个时候消息队列就是最好的解决办法了。意思从名字上就能看出来,削弱高峰时候的冲击。
刚刚说了一下消息队列的主要作用,这里继续说说消息队列缺点(这个也是网上查阅的资料): - 系统可用性降低:你想啊,本来其他系统只要运行好好的,那你的系统就是正常的。现在你非要加个消息队列进去,那消息队列挂了,你的系统不是呵呵了。因此,系统可用性降低
- 系统复杂性增加:要多考虑很多方面的问题,比如一致性问题、如何保证消息不被重复消费,如何保证保证消息可靠传输。因此,需要考虑的东西更多,系统复杂性增大。
redis做秒杀
刚刚因为我这篇文章主旨是用redis做个简单的秒杀demo,不过消息队列是一个首要的需要理解的知识点。顺便又写了下消息队列的一些常用知识,接下来回到正题吧。为什么用redis我也说明白了。
说一下大致有几点注意的:
- redis中没有整数型,所以value是string类型。
- 因为一种直接的秒杀是付款算是完成。还有一种是支付前就可以锁定。付款才算完成比较少见而且不太合理,所以这里要考虑一个锁定后不支付的问题。。
3.同一用户只能抢购一次(我看好多地方说的是这个防止用户恶意刷商品数量或者用脚本啥的非正常竞争。)
然后我做的思路很简单,如果说不考虑可能取消订单的情况,就是key是商品id,value是抢购数量。然后这个来一个人抢购redis中value-1.一直到等于0.这块代码块用异步处理,可以防止出现超卖/超买现象。
如果考虑取消订单的情况,我的做法就是
redis中,key是商品id,value起始值是商品数量但是它也是一个实时获取的值。另外用商品id+用户id作为key,value自定义,超时时间为付款时间(一般半小时,15分钟等等)。正常一次抢购访问,先判断value是否大于0,如果是则减一,并且生成一条此商品购买记录。(此两个步骤要保证原子性。最好在一个方法里用锁)
如果用户手动取消订单则直接删除该用户商品购买记录的key。如果用户完成支付流程,将此key的过期时间设置为无限期或者约定时长(一周,一天之类的)。
定时查询key是商品id的模糊查询条数。并用总商品数-商品购买记录,得到的值改写到key是商品id的value中。如果获取到的值是0,则表示此商品已全部卖完。
至此,我用redis的秒杀完成。
因为我测试用的demo比较简陋,所以在此就不贴出来了。因为代码上没有什么难点,主要是思路。然后如果大家有更好的实现方法欢迎提出或者交流。
主要是在网上说起秒杀都是直接就看做支付,很少有考虑还有退单的可能啊。我觉得我的方法也比较麻烦,应该是有更好的办法的,大神们欢迎指教。
然后全文手打不易,亲们,如果觉得帮到你了点个喜欢点个关注支持一下哟~~~~~么么哒!