秒杀系统解决两个问题:一个是并发读、一个是并发写。
对应到实际的系统开发在于高可用、一致性、高性能。
高性能
- 前端页面进行动静分离,将静态页面缓存。
在实际的秒杀系统中我们发现其实只有秒杀时间在动态的变化。因此我们可以将用户的个人数据以及秒杀时间等动态数据与静态数据进行分离,将静态数据进行缓存。
- 缓存技术:浏览器缓存,服务端缓存,cdn缓存
- 浏览器缓存:需要用户刷新页面才可以进行变化,系统很难主动的将消息推送给用户,导致很长一段时间看到的页面都是不变的。
- 服务端缓存:将页面缓存到服务端。在我的这个系统中就是将相对静态的页面比如商品详情页这些缓存到服务端redis中,采用springwebcontex与thymeleaf方法对其进行渲染放到缓存中,然后直接从缓存中取。然后根据系统要求设置对应的过期时间。
- cdn缓存:cdn缓存是一种可能在实际系统中更可用的方案。但是将数据放到cdn缓存中需要考虑缓存失效问题与高命中问题。保证系统在秒级时间内统一失效和高命中率是比较关键的。
数据整合。将静态数据与动态数据整合主要有三种方案。
- CSI:服务器只返回静态页面,前端通过JS异步请求动态数据。本系统就是通过ajax异步对数据进行获取。用户的体验度较低。
- SSI:采用相对应的SSI注释行命令加载页面。缺点是不能加载包含其他服务器上的文件。
- ESI:先请求动态数据再将动态数据与静态数据一起返回,用户看到的是一个完整的页面。对服务其的性能要求较高。
- 热点操作与热点数据进行优化
- 一般来说,零点刷新、下单与添加购物车都属于热点操作,可以提前对这些操作设置一些限制,比如限制用户点击的最大次数或者在某个时间段内不能重复点击。本系统就限制了某个时间段内的最大点击数,同样将点击次数放在redis中。
热点数据的处理
- 静态热点数据。可以提前将热点商品数据整理出来放入缓存中。
- 动态热点数据。在实际秒杀过程中异步采集每个环节的热点key信息然后进行相关聚合分析对其进行处理。
热点数据隔离
- 我们可以将整个业务进行分离。将这个业务申请单独的域名进行相关处理。
- 可以将秒杀系统的数据库单独分离部署,以免影响到其他业务。
本系统将待秒杀商品的信息放入redis缓存中,然后使用rabbitmq消息队列对其进行限流。
一致性
一致性我觉得就是一个库存和重复下单的问题。
这在我在实际开发的过程中也有遇到过,在把秒杀下单的基本逻辑实现后采用jmeter进行压测时发现存在库存为负与一个用户重复下单的情况。
针对超卖这个问题首先将所有的sql语句都进行了库存的判断,但发现还是会有问题,然后尝试使用了redis分布式锁进行实现,解决了超卖这个问题,但是发现带来的消耗很大。
针对重复下单这个问题,在刚开始和最后的下单的时候对数据库采用count 1这样的指令对用户是否重复下单进行判断,另外也在数据库中建立唯一索引。
最后通过redis预减库存和rabbitmq异步处理订单消息解决一致性的问题。
将库存放到redis缓存中,通过redis缓存进行预减,设置一个hashmap,当对应商品的库存为0时将标志位置为false,减少对redis的访问。
rabbitmq异步处理,库存充足,订单生成成功,更新数据库。
高可用
保证秒杀系统的可用性,需要削减流量峰值
- 本系统首先在采用验证码操作在入口处就削减流量峰值
- 采用rabbitmq消息队列来异步处理消息