秒杀项目总结
0、简单讲一下高并发的秒杀系统
这个系统的基本框架是黑马程序员的瑞吉外卖项目,在实现外卖项目的所有功能业务之后,在此基础上,改进了添加了高并发的秒杀功能。通过根据场景不断的迭代秒杀成为支持高并发的高性能系统。为了解决秒杀场景下的高并发问题,进行了一些几种手段的优化:
1、引入Redis作为缓存中间件,主要作用是缓存预热、预减库存等。
2、页面优化,缓存到浏览器,前后端分离降低服务器压力,加快访问速度。
3、安全性上,使用双重MD5加密,隐藏秒杀接口,设置限流防刷,数学公式验证防刷。
4、Jmeter压测秒杀接口,系统QPS从50/s提升到500/s。
0、秒杀系统面临的问题?
- 高并发场景
- 超卖、重复下单的问题
- 脚本恶意访问
- 数据库频繁交互扛不住
- 加了缓存之后缓存的三大问题(击穿、穿透、雪崩)
Redis来判断是否符合秒杀请求的下单资格,符合的话在redis中进行库存预减,同时将此消息放入消息队列,消息队列的消费者负责从队列中获取消息进行消费(执行真正的生成订单、操作数据库减库存的操作)
保证不出错的点:
1、redis减库存和消息生成一对一
2、消息队列必须保证消息的完整消费
3、
1、秒杀中超卖问题
在Redis中预减库存时利用redis的单线程特性处理秒杀超卖问题!
实现流程:
- 系统初始化时,将秒杀商品的库存量预先加载存储到redis中(缓存预热)
- 秒杀请求接收到的时候,在redis中进行预减库存(decrement),库存不足时,秒杀失败,判断大于0时进行下面操作。(decrement、increment)为原子操作,线程安全。
- 将请求的用户实体和秒杀的商品实体序列化之后放入异步队列(消息中间件发送),返回秒杀排队中。
- 服务端消息接收器进行消息的消费,也就是要不队列的出队,然后将消息反序列化为实体,执行数据库的减库存,并生成订单,返回订单详情。
在数据库库存减1时,依靠MySQL中的排它锁实现。
boolean result = seckillGoodsService.update(new UpdateWrapper<SeckillGoods>().setSql("stock_count ="+
"stock_count-1").eq("id",seckillGoods.getId()).gt("stock_count",0));
2、减库存成功,订单生成失败怎么办?
非分布式系统中使用Springboot提供的事务功能即可。
分布式系统事务:将减库存与生成订单操作组合为一个事务。要么一起成功,要么一起失败。分布式事务方案
3、限流削峰的措施?
- 令牌桶算法接口限流
- 线程池技术限制并发数
- 数学公式验证码防刷功能
令牌桶算法:
令牌桶算法和漏桶算法不同的是,有时后端能够处理一定的突发情况,只是为了系统稳定,一般不会让请求超过正常情况的60%,给容灾留有余地。但漏桶算法中后端处理速度是固定的,对于短时的突发情况,后端不能及时处理,和实际处理能力不匹配。
令牌算法是以固定速度往一个桶内增加令牌,当桶内令牌满了后,就停止增加令牌。上游请求时,先从桶里拿一个令牌,后端只服务有令牌的请求,所以后端处理速度不一定是匀速的。当有突发请求过来时,如果令牌桶是满的,则会瞬间消耗桶中存量的令牌。如果令牌还不够,那么再等待发放令牌(固定速度),这样就导致处理请求的速度超过发放令牌的速度。
如下图,灰色部分是令牌桶,有容量限制,只能最多存放 capacity 个令牌,每秒以固定的速度向桶中增加令牌,如果桶的容量满了,则等待桶中令牌被消耗后,再增加令牌。另一边应用进程拿到令牌后才处理请求,如果没有拿到令牌,则不处理该请求。
1 有一个固定容量的桶存放令牌(Token)。
2 桶初始化是空的,以固定的速度(rate)向桶里填充 Token,当达到桶的容量时,多余的令牌被丢弃。
3 当请求到来时,从桶里移除一个令牌,如果没有令牌,则拒绝该请求。
令牌桶控制的是令牌入桶的速度,对于拿到令牌的速度没有限制,允许一定的突发流量被瞬间处理。
4、安全性问题
- 封IP,nginx中可以设置单个ip访问频率和次数多了有拉黑操作
- 隐藏秒杀接口地址
- 验证码防止恶意刷
5、多机器扣减库存,如何保证线程安全?
多机器意味着分布式场景,此时的redis肯定是分布式的,有多台redis服务器,可以用分布式锁来解决这个问题,具体实现可以用redission客户端实现。
redis分布式锁详见另一篇文章redis分布式锁的进化
6、缓存类问题:
缓存失效问题详解:缓存失效的问题及持久化
- 缓存数据突然全部失效,导致请求全部打到数据库,如何解决?(缓存雪崩)
答案:典型的缓存雪崩问题,给缓存中的数据的过期时间加随机数。 - 热点数据失效问题如何解决?(缓存击穿)
答案:设置热点数据永远不过期。
7、Redis突然挂掉,如何减轻数据库压力?
Redis集群模式:主从模式、哨兵模式、集群模式
主从模式:
主机宕机,主机断开,从机升级为主机。
哨兵模式:
每个sentinel会向其它sentinel、master、slave定时发送消息(哨兵定期给主或者从和slave发送ping包(IP:port),正常则响应pong,ping和pong就叫心跳机制)如果发现消息在指定时间内没有回复,则判定主机为宕机,判定死亡之后,通过选举算法,从剩下的slave节点中选举产生新的主节点,自动配置同步问题。
8、分布式会话的问题?
分布式会话可以通过session、cookie或者token的手段来解决,解析请看
在我的秒杀项目中,采用的session的方式来解决这个登录问题,不同的浏览器、不同的用户、不同的电脑访问服务端时会生成不同的sessionid,传回给浏览器,存放在cookie中。二次登录时,从请求中获取到sessionid并判断是否存在,如果存在,则说明已经登录,可以进行其他操作,如果不存在,则说明未登录或者登录失效,重新登录。这些判断操作等处理在服务端的拦截器中进行。
9、项目中redis和rabbitmq的作用
redis
redis中库存减成功后,生成一条消息包含了商品信息、用户信息消息由MQ的生产者生产,经由queue模式发送给消费方,即订单生成的业务模块,在该模块会消费这条消息,根据其中的信息进行订单的生成,以及数据库的修改操作。
运用三级存储:内存、redis、数据库
- 作为缓存中间件提升系统性能
- 预减库存,防止超卖功能实现
- redis设置热点数据永不过期
rabbitmq(rabbitmq可以看其他系列文章)
作为异步下单的中间件,利用队列排队下单缓解数据库的并发压力。
10、线程池的应用
线程池的线程数的设置经验:
设置最大线程数来限制浪涌流量
- CPU密集型业务:N+1
- IO密集型业务:2N+1