项目的亮点和难点及问题解决

【商城限时秒杀系统总结】php

在高并发状况下的秒杀优化,咱们知道当并发数达到必定量的时候,会对数据库服务器带来很大的压力,那么如何缓解这些压力以及提升并发的QPS就是整个项目的解决重点,也是咱们优化系统的目标。html


源码地址:https://github.com/pitt1997/Seckill前端

项目的亮点:git

1.使用分布式Seesion,能够实现让多台服务器同时能够响应。github

2.使用redis作缓存提升访问速度和并发量,减小数据库压力,利用内存标记减小redis的访问。golang

3.使用页面静态化,加快用户访问速度,提升QPS,缓存页面至浏览器,先后端分离下降服务器压力。ajax

4.使用消息队列完成异步下单,提高用户体验,削峰和降流。redis

5. 安全性优化:双重md5密码校验,秒杀接口地址的隐藏,接口限流防刷,数学公式验证码。算法


主要知识点:sql


分布式Seesion

咱们的秒杀服务,实际的应用可能不止部署在一个服务器上,而是分布式的多台服务器,这时候假如用户登陆是在第一个服务器,第一个请求到了第一台服务器,可是第二个请求到了第二个服务器,那么用户的session信息就丢失了。

解决:session同步,不管访问那一台服务器,session均可以取获得,利用redis缓存的方法,另外使用一个redis服务器专门用于存放用户的session信息。这样就不会出现用户session丢失的状况。(每次须要session,从缓存中取便可)


redis缓解数据库压力

本项目大量的利用了缓存技术,包括用户信息缓存(分布式session),商品信息的缓存,商品库存缓存,订单的缓存,页面缓存,对象缓存减小了对数据库服务器的访问。


通用缓存key封装

大量的缓存引用也出现了一个问题,如何识别不一样模块中的缓存(key值重复,如何辨别是不一样模块的key)

解决:利用一个抽象类,定义BaseKey(前缀),在里面定义缓存key的前缀以及缓存的过时时间从而实现将缓存的key进行封装。让不一样模块继承它,这样每次存入一个模块的缓存的时候,加上这个缓存特定的前缀,以及能够统一制定不一样的过时时间。


页面静态化(先后端分离)

页面静态化的主要目的是为了加快页面的加载速度,将商品的详情和订单详情页面作成静态HTML(纯的HTML),数据的加载只须要经过ajax来请求服务器,而且作了静态化HTML页面能够缓存在客户端的浏览器。


消息队列完成异步下单

使用消息队列完成异步下单,提高用户体验,削峰和降流

思路:

1.系统初始化,把商品库存数量stock加载到Redis上面来。

2.后端收到秒杀请求,Redis预减库存,若是库存已经到达临界值的时候,就不须要继续请求下去,直接返回失败,即后面的大量请求无需给系统带来压力。

3.判断这个秒杀订单造成没有,判断是否已经秒杀到了,避免一个帐户秒杀多个商品,判断是否重复秒杀。

4.库存充足,且无重复秒杀,将秒杀请求封装后消息入队,同时给前端返回一个code (0),即表明返回排队中。(返回的并非失败或者成功,此时还不能判断)

5.前端接收到数据后,显示排队中,并根据商品id轮询请求服务器(考虑200ms轮询一次)。

6.后端RabbitMQ监听秒杀MIAOSHA_QUEUE的这名字的通道,若是有消息过来,获取到传入的信息,执行真正的秒杀以前,要判断数据库的库存,判断是否重复秒杀,而后执行秒杀事务(秒杀事务是一个原子操做:库存减1,下订单,写入秒杀订单)。

7.此时,前端根据商品id轮询请求接口MiaoshaResult,查看是否生成了商品订单,若是请求返回-1表明秒杀失败,返回0表明排队中,返回>0表明商品id说明秒杀成功。


安全性优化

双重md5密码校验,秒杀接口地址的隐藏,接口限流防刷,数学公式验证码。


优雅的代码编写

接口的输出结果作了一个Result封装

对错误的代码作了一个CodeMsg的封装

访问缓存作了一个key的封装


项目难点及问题解决:

1. 使用JMeter作压测的时候开启5000个线程,系统跑不起来,出现异常

缘由:修改配置文件中redis的配置项poolMaxTotal 将其设置成1000。


#redis配置项

redis.poolMaxTotal=1000

redis.poolMaxldle=500

redis.poolMaxWait=500

1

2

3

4

2.使用了大量缓存,那么就存在缓存击穿和缓存雪崩以及缓存一致性等问题?

缓存穿透指的是对某个必定不存在的数据进行请求,该请求将会穿透缓存到达数据库。

解决方案:对这些不存在的数据缓存一个空数据,对这类请求进行过滤。


缓存雪崩指的是因为数据没有被加载到缓存中,或者缓存数据在同一时间大面积失效(过时),又或者缓存服务器宕机,致使大量的请求都到达数据库。

解决方案:

为了防止缓存在同一时间大面积过时致使的缓存雪崩,能够经过观察用户行为,合理设置缓存过时时间来实现;

为了防止缓存服务器宕机出现的缓存雪崩,可使用分布式缓存,分布式缓存中每个节点只缓存部分的数据,当某个节点宕机时能够保证其它节点的缓存仍然可用。

也能够进行缓存预热,避免在系统刚启动不久因为还未将大量数据进行缓存而致使缓存雪崩。

例如:首先针对不一样的缓存设置不一样的过时时间,好比session缓存,在userKey这个前缀中,设置是30分钟过时,而且每次用户响应的话更新缓存时间。这样每次取session,都会延长30分钟,相对来讲,就减小了缓存过时的概率


缓存一致性要求数据更新的同时缓存数据也可以实时更新。


解决方案:

在数据更新的同时当即去更新缓存,首先尝试从缓存读取,读到数据则直接返回;若是读不到,就读数据库,并将数据会写到缓存,并返回。

在读缓存以前先判断缓存是不是最新的,若是不是最新的先进行更新,须要更新数据时,先更新数据库,而后把缓存里对应的数据失效掉(删掉)。


3.大量的使用缓存,对于缓存服务器,也有很大的压力,思考如何减小redis的访问?

在redis预减库存的时候,内存中维护一个isOvermap做为一个内存标记,当没有库存的时候,将其置为true。每次秒杀业务访问redis以前,查一下map标记,若是true说明没有库存,就直接返回失败,无需再去请求redis服务器。


4.在高并发请求的业务场景,大量请求来不及处理,甚至出现请求堆积时候?

消息队列,用来异步处理请求。每次请求过来,先不去处理请求,而是放入消息队列,而后在后台布置一个监听器,分别监听不一样业务的消息队列,有消息来的时候,才进行秒杀业务逻辑。这样防止多个请求同时操做的时候,数据库链接过多的异常。


5.怎么保证一个用户不能重复下单?

解决:秒杀订单表中创建一个惟一索引(所引是用户Id与商品goodsId),使得第一个记录能够插入,第二个则出错,而后经过事务回滚,防止一个用户同时发出多个请求的处理,秒杀到多个商品。


惟一索引,便是惟一的意思,在数据库表结构中对字段添加惟一索引后进行数据库进行存储操做时数据库会判断库中是否已经存在此数据,不存在此数据时才能进行插入操做。


这虽然是个小技能,但实际上在业务开发中是个很实用的技能,好比在高并发业务中,数据库如何杜绝数据并发插入两条相同的订单号呢?添加一个惟一索引固然是最快捷的方法之一,固然是添加索引仍是经过业务代码去解决因公司业务而定


6.怎么解决超卖现象?

超卖场景:不一样用户在读请求的时候,发现商品库存足够,而后同时发起请求,进行秒杀操做,减库存,致使库存减为负数。


最简单的方法,更新数据库减库存的时候,进行库存限制条件,在reduceStock(GoodsVo goodsvo)这个方法里,sql要多加一个stock_count > 0 ,使用数据库特性来保证超卖的问题,只有stock_count还大于0的时候才去读stock_count而后减1操做


@Update("update miaosha_goods set stock_count=stock_count-1 where goods_id=#{goodsId} and stock_count>0")

public void reduceStock(MiaoshaGoods goods);  

1

2

7.页面静态化的过程及什么是浏览器缓存?

将HTML静态页面缓存在客户端浏览器,只有数据经过ajax异步调用接口来获取,仅仅交互的是部分数据,减小了带宽,也加快用户访问的速度。


浏览器缓存就是把一个已经请求过的Web资源(如html页面,图片,js,数据等)拷贝一份副本储存在浏览器中。缓存会根据进来的请求保存输出内容的副本。当下一个请求来到的时候,若是是相同的URL,缓存会根据缓存机制决定是直接使用副本响应访问请求,仍是向源服务器再次发送请求。比较常见的就是浏览器会缓存访问过网站的网页,当再次访问这个URL地址的时候,若是网页没有更新,就不会再次下载网页,而是直接使用本地缓存的网页。只有当网站明确标识资源已经更新,浏览器才会再次下载网页。


8.秒杀架构设计理念?

限流:鉴于只有少部分用户可以秒杀成功,因此要限制大部分流量,只容许少部分流量进入服务后端。


削峰:对于秒杀系统瞬时会有大量用户涌入,因此在抢购一开始会有很高的瞬间峰值。高峰值流量是压垮系统很重要的缘由,因此如何把瞬间的高流量变成一段时间平稳的流量也是设计秒杀系统很重要的思路。实现削峰的经常使用的方法有利用缓存和消息中间件等技术。


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


内存缓存:秒杀系统最大的瓶颈通常都是数据库读写,因为数据库读写属于磁盘IO,性能很低,若是可以把部分数据或业务逻辑转移到内存缓存,效率会有极大地提高。


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


9.秒杀系统架构设计思路?

将请求拦截在系统上游,下降下游压力:秒杀系统特色是并发量极大,但实际秒杀成功的请求数量却不多,因此若是不在前端拦截极可能形成数据库读写锁冲突,最终请求超时。

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

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

10.假如减了库存用户没有支付,库存怎么还原继续参加抢购?

设定一个最长付款时间,好比30分钟,后台有个定时任务(使用定时器Timer),轮训超过30分钟的待付款订单(数据库里面断定订单状态),而后关闭订单,恢复库存。

本文分享自微信公众号 - golang算法架构leetcode技术php(golangLeetcode)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索