您的位置:首页 > 职场人生

Java开发面试:高并发秒杀系统如何设计与优化

2017-08-31 11:28 1111 查看
      如今处在一个大数据时代,应届生找工作面试高级Java开发工程师时,经常会被问一些和大数据相关的问题,比如大数据处理问题、高并发处理问题、数据优化问题等,笔者曾经遇到两个比较经典的问题,高并发秒杀系统的设计优化问题和大数据文件排序问题。在这里总结了高并发秒杀系统的设计和优化点。 

面试官常问的问题有:

简单说一下秒杀系统的设计思路?

你怎么实现秒杀业务的?

你怎么保证秒杀成功的?

秒杀操作的策略是什么?

你使用的Redis有什么用?

你为什么使用Redis中间件?

你测试过你这个系统的抗压能力么?

你使用过什么方法来测试你的系统并发量?

你觉得你这个系统还可以再优化么?

你觉得你这个系统的瓶颈在哪里?还可以在哪些方向做进一步优化?

      很多面试官问的比较笼统,很少面试官问的比较具体,事先的这些问题,都要做好准备,笔者的准备思路是:

                   功能模块划分=》秒杀策略=》自己的优化点=》工具测试抗压=》别人提供的优化方法和秒杀策略

1 秒杀系统特点

秒杀业务简单,卖家查询,买家下订单减库存。
秒杀时网站访问流量激增,出现峰值;
访问请求数量远大于实际需求量。

2 架构设计优化方案

1 秒杀系统架构设计优化

        一个常规的秒杀系统从前到后,依次有:

                  前端浏览器秒杀页面=》中间代理服务=》后端服务层=》数据库层

      根据这个流程,一般优化设计思路:将请求拦截在系统上游,降低下游压力。在一个并发量大,实际需求小的系统中,应当尽量在前端拦截无效流量,降低下游服务器和数据库的压力,不然很可能造成数据库读写锁冲突,甚至导致死锁,最终请求超时。 

整体设计思路和优化点:

限流:屏蔽掉无用的流量,允许少部分流量流向后端。

削峰:瞬时大流量峰值容易压垮系统,解决这个问题是重中之重。常用的消峰方法有异步处理、缓存和消息中间件等技术。

异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理就是削峰的一种实现方式。

内存缓存:秒杀系统最大的瓶颈一般都是数据库读写,由于数据库读写属于磁盘IO,性能很低,如果能够把部分数据或业务逻辑转移到内存缓存,效率会有极大地提升。

可拓展:当然如果我们想支持更多用户,更大的并发,最好就将系统设计成弹性可拓展的,如果流量来了,拓展机器就好了。像淘宝、京东等双十一活动时会增加大量机器应对交易高峰。

消息队列:消息队列可以削峰,将拦截大量并发请求,这也是一个异步处理过程,后台业务根据自己的处理能力,从消息队列中主动的拉取请求消息进行业务处理。

充分利用缓存:利用缓存可极大提高系统读写速度。 

2详细方案

                    


2.1 前端方案

静态资源缓存:将活动页面上的所有可以静态的元素全部静态化,尽量减少动态元素;通过CDN缓存静态资源,来抗峰值。 

禁止重复提交:用户提交之后按钮置灰,禁止重复提交 

用户限流:在某一时间段内只允许用户提交一次请求,比如可以采取IP限流

2.2 中间代理层

            可利用负载均衡(例如反响代理Nginx等)使用多个服务器并发处理请求,减小服务器压力。

2.3 后端方案

控制层(网关层)

            限制同一UserID访问频率:尽量拦截浏览器请求,但针对某些恶意攻击或其它插件,在服务端控制层需要针对同一个访问uid,限制访问频率。

服务层

当用户量非常大的时候,拦截流量后的请求访问量还是非常大,此时仍需进一步优化。

1.业务分离:将秒杀业务系统和其他业务分离,单独放在高配服务器上,可以集中资源对访问请求抗压。

2.采用消息队列缓存请求:将大流量请求写到消息队列缓存,利用服务器根据自己的处理能力主动到消息缓存队列中抓取任务处理请求,数据库层订阅消息减库存,减库存成功的请求返回秒杀成功,失败的返回秒杀结束。

3.利用缓存应对读请求:对于读多写少业务,大部分请求是查询请求,所以可以读写分离,利用缓存分担数据库压力。

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

2.4 数据库层

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

       如果不使用缓存来作为中间缓冲而是直接访问数据库的话,可以对数据库进行优化,减少数据库压力。

       对于秒杀系统,直接访问数据库的话,存在一个【事务竞争优化】问题,可使用存储过程(或者触发器)等技术绑定操作,整个事务在MySQL端完成,把整个热点执行放在一个过程当中一次性完成,可以屏蔽掉网络延迟时间,减少行级锁持有时间,提高事务并发访问速度。

2.5 其他秒杀策略

减少硬件开销的策略 :

       策略1:消息队列缓存请求,按照队列模型取任务执行,秒杀完毕即终止到秒杀结束页面。

       策略2:使用数组为并发请求随机分配秒杀状态(成功和失败),然后将分配到失败状态的请求派发到秒杀失败的页面,分到成功状态的用户在慢慢的按顺序执行秒杀操作;(如果处理失败了可以利用日志来查找具体秒杀失败的商品和用户,执行补救措施或者从其他用户中拿取一个来执行秒杀操作)

       策略3:类似于策略2,不过是用数组为用户分配秒杀资格,将大流量的用户限制为小流量的用户,得到秒杀资格的去执行秒杀,得不到秒杀资格的跳到秒杀失败页面。 

(分配状态或分配秒杀资格的策略:(数组状态密度不同,由前到后逐渐稀疏,可以让先到的在前面随机分配,后到的在后面随机分配)根据先到的时间)

3 案例:利用消息中间件和Redis缓存实现

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

       我们可以采用Redis 最简单的key-value数据结构,用一个原子类型的变量值(AtomicInteger)作为key,把用户id作为value,库存数量便是原子变量的最大值。对于每个用户的秒杀,我们使用 RPUSH key value插入秒杀请求, 当插入的秒杀请求数达到上限时,停止所有后续插入。

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

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

 

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

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

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

      JVM 内存较小,隔一段时间会自动进行垃圾回收。

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

 

3 压力测试

使用JMeter 压测工具

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

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

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

点击运行-》启动。

并发量:50W-100W    100W-500W
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息