JavaWeb秒杀业务场景设计

96
小红牛
2018.06.11 15:25 字数 2109

秒杀业务场景设计问题经常被面试的时候被问到,在实际业务中,也常常需要实现,下面我们来看看如何实现秒杀业务.

秒杀业务,是典型的短时大量突发访问类问题

特点:
秒杀时网站的访问量大增;
秒杀时购买的请求数量远小于库存,只有部分用户能够成功;
业务流程简单,根据先后顺序,下订单减库存;

首先看一下普通商品购买业务的基本逻辑


C1.png

那么,秒杀业务,会影响到上面的哪些方面呢

前端:

在同一时段,大量的用户集中访问前端页面的资源,流量剧增,导致页面刷新不及时,甚至无法访问,秒杀系统特点是并发量极大,但实际秒杀成功的请求数量却很少,所以如果不在前端拦截很可能造成数据库读写锁冲突,甚至导致死锁,最终请求超时;

解决方案:
(1).当流量过大的时候,加一个验证码可以在单位时间内有效的控制住合法用户;
(2).将活动页面上的所有可以静态的元素全部静态化,并尽量减少动态元素,通过CDN来抗峰值;
(3).用户提交之后按钮置灰,禁止重复提交,在某一时间段内只允许用户提交一次请求,比如可以采取IP限流;

后端:

传统秒杀系统之所以挂,请求都压倒了后端数据层,数据读写锁冲突严重,并发高响应慢,几乎所有请求都超时,流量虽大,下单成功的有效流量甚小【一趟火车其实只有2000张票,200w个人来买,基本没有人能买成功,请求有效率为0】

解决方案:

站点层设计

前端层的请求拦截,只能拦住小白用户(不过这是99%的用户哟),高端的程序员根本不吃这一套,写个for循环,直接调用你后端的http请求,怎么整?

(1)同一个uid,限制访问频度,做页面缓存,x秒内到达站点层的请求,均返回同一页面

(2)同一个item的查询,例如手机车次,做页面缓存,x秒内到达站点层的请求,均返回同一页面

如此限流,又有99%的流量会被拦截在站点层。

服务层设计

站点层的请求拦截,只能拦住普通程序员,高级黑客,假设他控制了10w台肉鸡(并且假设买票不需要实名认证),这下uid的限制不行了吧?怎么整?

(请求队列,cache缓存)

(1)大哥,我是服务层,我清楚的知道小米只有1万部手机,我清楚的知道一列火车只有2000张车票,我透10w个请求去数据库有什么意义呢?对于写请求,做请求队列,每次只透过有限的写请求去数据层,如果均成功再放下一批,如果库存不够则队列里的写请求全部返回“已售完”;

(2)对于读请求,还用说么?cache来抗,不管是memcached还是redis,单机抗个每秒10w应该都是没什么问题的;

如此限流,只有非常少的写请求,和非常少的读缓存mis的请求会透到数据层去,又有99.9%的请求被拦住了。

上面只拦截了一部分访问请求,当秒杀的用户量很大时,即使每个用户只有一个请求,到服务层的请求数量还是很大。比如我们有100W用户同时抢100台手机,服务层并发请求压力至少为100W。

(1). 采用消息队列缓存请求:既然服务层知道库存只有100台手机,那完全没有必要把100W个请求都传递到数据库啊,那么可以先把这些请求都写到消息队列缓存一下,数据库层订阅消息减库存,减库存成功的请求返回秒杀成功,失败的返回秒杀结束。

(2). 利用缓存应对读请求:对类似于12306等购票业务,是典型的读多写少业务,大部分请求是查询请求,所以可以利用缓存分担数据库压力。

(3). 利用缓存应对写请求:缓存也是可以应对写请求的,比如我们就可以把数据库中的库存数据转移到Redis缓存中,所有减库存操作都在Redis中进行,然后再通过后台进程把Redis中的用户秒杀请求同步到数据库中。
数据库层

数据库层是最脆弱的一层,一般在应用设计时在上游就需要把请求拦截掉,数据库层只承担“能力范围内”的访问请求。所以,上面通过在服务层引入队列和缓存,让最底层的数据库高枕无忧。

接下来详细讲Redis缓存详细思路;

Redis是一个分布式缓存系统,支持多种数据结构,可利用Redis轻松实现一个强大的秒杀系统。

实现原理:list双向链表

此处用到了Redis中的链表(list)数据类型:


C2.png

C3.png

我们可以采用Redis 最简单的key-value数据结构,用一个原子类型的变量值(AtomicInteger)作为key,把用户id作为value,库存数量便是原子变量的最大值。对于每个用户的秒杀,我们使用 RPUSH key value插入秒杀请求, 当插入的秒杀请求数达到上限时,停止所有后续插入。因为pop操作是原子的,即使有很多用户同时到达,也是依次执行.(mysql事务在高并发下性能下降很厉害,文件锁的方式也是).

然后我们可以再启动多个工作线程,使用 LPOP key 读取秒杀成功者的用户id,然后再操作数据库做最终的下订单减库存操作。

当然,上面Redis也可以替换成消息中间件如ActiveMQ、RabbitMQ等,也可以将缓存和消息中间件 组合起来,缓存系统负责接收记录用户请求,消息中间件负责将缓存中的请求同步到数据库。

(1)使用Redis中间件缓存动态资源的好处?

提高访问速度,减少对数据库的链接的打开、关闭,

(2)为什么不用JVM内存而使用Redis作为缓存呢?

JVM 内存较小,隔一段时间会自动进行垃圾回收。
JVM和业务程序绑定在一起了,如果程序出错,JVM也会停止,这样就导致缓存数据丢失。
如果使用Redis,除了缓存比较大之外,还实现了缓存数据和业务程序的分离,即使运行程序出现错误,也不会影响缓存。

压力测试工具

使用JMeter 压测工具

下载、安装、进入C:/JMeter/bin下面的jmeter.bat批处理文件来启动JMeter的可视化界面,

进入测试计划添加线程组: 设置线程数,循环次数,添加HTTP默认请求,服务器名称,IP,以及自己设定的携带参数

添加监听器,存放测试结果:聚合报告,可以表格查询、图形结果、树结果

点击运行-》启动。

并发量:50W-100W 100W-500W

推荐阅读:《秒杀系统企业级实战应用(真实工业界案例)》https://www.roncoo.com/course/view/086c2c4027ac4a00aa1e9d63d7bac36d

文章来源:https://blog.csdn.net/weixin_40099554/article/details/79895624

随笔