ja抽奖程序 ja随机抽奖程序

专注Ja领域的优质技术,欢迎关注作者:爱钓鱼的桌子,灵猫技术窝

1、抽奖系统的背景引入

本文将和大家分享一个我们之前经历过的系统流量削峰架构的设计方案。

抽奖、抢红包、秒杀都有一个共同的特点,就是在某个时间点,会有大量的人瞬间涌入系统点击,给系统造成比平时高出几百倍、几千倍甚至几十万倍的流量压力。

比如抽奖环节,有一个场景:某个网站或APP规定,在某个时间点,所有人都可以参与抽奖,那么也许几百万用户会停留在那个时间点,所有人一起参与抽奖。

抢红包,可能在一个电视节目上,突然说扫码可以抢红包,那么电视机前的千万用户可能瞬间打开手机扫码抢红包。

《秒杀》更是如此。所谓秒杀,就是让大家守在电脑前,在某个时间突然抢购限量商品。

比如一部手机,平时卖5999,现在限量100套,折扣2999,50%。可能几百万用户会在凌晨12点蹲在电脑前一起点击按钮抢购这款手机。

其实现在类似的场景有很多,所以本文以一个系统为例,谈谈如何设计一个调峰架构来应对这种瞬时超高的并发流量,保证系统不会突然穿越。

2、结合具体业务需求分析抽奖系统

假设现在有一个抽奖的业务场景,用户可以在某个时间参与抽奖。比如有一万个奖品,奖品是赠品。

然后可能会有几十万用户参与抽奖。一瞬间,可能几十万个请求涌入,然后一瞬间,其中一万个中了奖,剩下的没中。然后10000名获奖者的请求将被链接到调用礼品服务,以完成10000名获奖者的礼品分发。

简单来说,这就是需求场景,但是这里有很多值得优化的地方。

3、一个未经过优化的系统架构

我们来看一个没有优化的系统架构。简单来说,就是有一个负载均衡的设备,将瞬间涌入的超高并发流量转发给后台服务。

这个抽奖服务是用普通的Tomcat部署的,具体的抽奖逻辑是在里面实现的。假设最常规的抽奖逻辑首先基于MySQL实现,然后基于Tomcat部署礼品服务。如果服务发现自己中奖了,需要调用礼品服务分发礼品。

如下图所示:

java抽奖程序 java随机抽奖程序

4、负载均衡层的限流

4.1防止用户重复抽奖。

第一次在负载均衡层可以做的第一件事就是防止重复抽签。

我们可以在负载均衡设备中做一些配置,判断如果同一个用户在一分钟内多次发送抽奖请求,就被认为是恶意重复抽奖,或者是他们编写的脚本在抽奖,这个流量就全部被认为是无效流量,直接在负载均衡设备层面进行屏蔽。

比如几十万用户同时抽奖,其实最多也就几十万个请求,但是如果有人重复抽奖或者写脚本刷奖,瞬间涌入的可能是几百万个请求,而不是几十万个请求,所以这里可以拦截无效流量。

如下图所示:

4.2全部抽奖结束后暴力拦截流量。

其实秒杀、抢红包、抽奖都有一个共同的特点,就是假设50万个请求涌入,前5万个请求可能直接就完了,甚至前500个请求都完了,后续几十万流量无效,不需要进入后台系统执行业务逻辑。

你什么意思?

比如《秒杀商品》。假设50万人抢一个专用手机,人们准备100个手机,那么50万个请求瞬间涌入。其实前500个请求已经把手机抢光了,后续几十万个请求没必要转发给Tomcat服务执行《秒杀》业务逻辑吧?

抽奖和红包是一样的。可能会有50万个请求涌入,但是前1万个请求已经全部抽到奖品,或者说红包已经全部被抢光。其实后续的流量是不需要放到Tomcat抽奖服务上的。直接拦截,回到抽奖结束就行了。

在这种情况下,实际上99%的无效流量都可以在负载均衡的层面被拦截(可以通过Nginx之类的来实现)。

因此,服务和负载均衡之间必须有一个状态共享机制。

也就是说,一旦所有的抽奖服务完成,就会直接更新一个分享状态。然后,在感测到负载平衡之后,所有后续请求都被拦截,并返回一个表示抽奖结束的标志。

这样做可能会让50万人一起请求,结果可能会有2万个请求到后台的Tomcat服务,48万个请求会被直接拦截。

我们可以基于Redis实现这种共享抽奖状态,Redis非常轻量级,适合两级系统的共享访问。

当然也可以用ZooKeeper。在负载平衡层,您可以基于zk客户端监控znode节点的状态。一旦抽奖结束,抽奖服务更新zk状态,负载均衡层会感觉到。

下图显示了上述过程:

5、Tomcat线程数量的优化

其次,对于在线生产环境下的Tomcat,有一个至关重要的参数需要根据自身情况进行调整,那就是工作线程的数量。

我们都知道,每一个进入Tomcat的请求,实际上都会被一个独立的工作线程处理,所以Tomcat有多少线程决定了处理并发请求的能力。

但是线程的数量需要通过压力测试来判断,因为每个线程都会处理一个请求,而这个请求需要访问数据库等外部系统,所以并不是每个系统的所有参数都可以相同,需要系统自己测试。

但是给定一个经验值,Tomcat中的线程数量应该不会太大。因为线程太多,普通虚拟机的CPU承受不了,反而会导致机器CPU过载,最终崩溃。

同时,Tomcat线程的数量也不能太少,因为如果只有100个线程,会导致无法充分利用Tomcat线程资源和机器的CPU资源。

所以一般来说,Tomcat的线程数量在200到500之间,但是多少需要你自己测试,不断调整参数,看具体的CPU负载和线程执行请求的效率。

当CPU负载可接受且请求执行性能正常时,尽可能增加线程数量。

但是如果达到一个临界值,发现机器负载过高,线程处理请求的速度开始下降,意味着这台机器无法处理这样的多线程并发处理请求,此时也无法继续增加线程数量。

6、基于Redis实现抽奖业务逻辑

现在问题又来了。虽然在负载均衡的层面已经拦截了50万流量中的48万,但仍有2万流量可能进入抽奖服务。

这时候业务自然可以多机部署。例如,如果一个Tomcat可以抵抗500个请求,那么20,000个并发机器就是40个机器。

如果基于云平台部署系统,只需临时租用一批机器进行活动,活动结束后机器即可发布。现在云平台很方便。

但是有一个问题。你的数据库MySQL能抗住20000个并发请求吗?

如果你实现了基于MySQL的核心业务逻辑,Tomcat部署的40个服务频繁的添加、删除、更改MySQL,这个MySQL实例很难抵挡。

所以这个时候MySQL就要被Redis取代了。通常,在这种场景下,建议基于Redis实现核心业务逻辑。

Redis可以在单机上抵抗20000个并发,这是一件非常容易的事情,所以这里需要进一步优化。如下图所示:

7、发放礼品环节进行限流削峰

那么问题又来了。假设服务的20000次请求中有10000次中奖,那么必然会导致服务调用礼品服务10000次。

假设礼物服务也是一个优化的Tomcat,可以抗500并发。礼品服务有必要配置20台机器吗?

其实这是没有必要的,因为抽奖结束后,礼品服务可以在后台慢慢发放中奖礼品,不需要一下子完成一万个请求的礼品发放逻辑。

因此,可以在服务和礼品服务之间引入消息中间件来限流和削峰。

也就是说,抽奖服务将中奖信息发送到MQ,然后礼物服务假设会部署两只Tomcat,从MQ慢慢消费中奖信息,再慢慢分发礼物。

假设两个礼品服务实例每秒可以完成100件礼品的分发,那么10000件礼品的分发将延迟100秒。

也就是你抽奖之后,可能一两分钟后就能看到自己礼物的一些物流配送的进度。

而且礼物服务可能需要在MySQL数据库中做很多操作,比如插入中奖记录,然后派送礼物等等。

此时由于gift服务中只有两个Tomcat实例,MySQL的并发读写不会太高,所以数据库层面也能抵抗。

整个过程如下图所示:

8、系统架构设计总结

其实对于商品秒杀、抽奖、抢红包这种制度,很多架构设计的思路都是类似的。核心思想是针对这种瞬时超高流量系统,尽可能在负载均衡层拦截99%的无效流量。

那么1%的流量进入核心业务服务后,每秒的并发可能还是几万,所以核心业务逻辑可以基于Redis实现,抵抗几万的并发。

最后,对于秒杀发货、抽奖发货、红包资金转账等非常耗时的操作,完全可以基于MQ进行限流削峰,后台有一个服务可以慢慢执行。

作者简介

钓鱼台哥哥,高级建筑师。

笔者曾就职于滴滴、百度、字节跳动等国内一线互联网公司,从事基础设施相关工作。他带领团队设计并构建了大型分布式存储系统、分布式消息中间件和分布式数据库,在分布式架构设计、高可用系统构建和基础中间件架构方面有着丰富的经验。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。

发表回复

登录后才能评论