这是我参与「第五届青训营 」伴学笔记创作活动的第 12 天
本堂课重点内容
高并发下秒杀系统的相关问题(1)
1.页面静态化+CDN解决静态资源的页面资源的返回
2.缓存问题
3.库存问题
详细知识点介绍
页面静态化+CDN解决静态资源的页面资源的返回
对于秒杀界面的静态资源请求的返回,如果直接访问服务端,服务端会因为并发量过大而挂掉,所以可以使用CDN(Content Delivery Network,即内容分发网络),使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。
缓存问题
引入redis缓存,可以避免大量请求直接查询数据库,导致数据库挂掉。
缓存问题之缓存击穿
缓存击穿是指由于redis缓存的冷不命中,导致大量相同请求同时访问数据库,导致数据库崩溃。
缓存击穿的解决办法是使用分布式锁,锁使得有且只有一个请求导致缓存的冷不命中,其他之后的请求都会命中。除此之外还应该在项目启动之前进行预热,预防冷不命中。
缓存问题之缓存穿透
如果请求id不存在或者缓存和数据库中都没有这个id,那么请求就会穿透缓存来访问数据库。我们可以在访问缓存之前就判断这个id在不在缓存或者数据库里面,而不是直接执行查询操作,这就是布隆过滤器,但是次这个技术使用的较少,最多的解决办法是将所有不存在的id也缓存起来。
库存问题
库存问题中包含两个,一个是库存不足和库存超卖。
这两个问题的涉及到了并发。并发问题是指多个线程使用和操作共享数据时,导致各个线程之间数据操作的不协调。如果多个请求判断库存是否不足和购买不是原子性的,会导致超卖现象。我们解决办法是使用lua脚本扣减库存。
lua脚本扣减库存
我们都知道lua脚本,是能够保证原子性的,它跟redis一起配合使用,能够完美解决上面的问题。lua脚本有段非常经典的代码:
StringBuilder lua = new StringBuilder();
lua.append("if (redis.call('exists', KEYS[1]) == 1) then");
lua.append(" local stock = tonumber(redis.call('get', KEYS[1]));");
lua.append(" if (stock == -1) then");
lua.append(" return 1;");
lua.append(" end;");
lua.append(" if (stock > 0) then");
lua.append(" redis.call('incrby', KEYS[1], -1);");
lua.append(" return stock;");
lua.append(" end;");
lua.append(" return 0;");
lua.append("end;");
lua.append("return -1;");
该代码的主要流程如下:
- 先判断商品id是否存在,如果不存在则直接返回。
- 获取该商品id的库存,判断库存如果是-1,则直接返回,表示不限制库存。
- 如果库存大于0,则扣减库存。
- 如果库存等于0,是直接返回,表示库存不足。
lua脚本判断和减库存原子性执行了,所以不会导致并发问题。
课后个人总结
高并发秒杀系统的设计需要考虑过滤不存在的请求,访问静态资源时不访问服务器,引入redis缓存来提高查询效率,启动之前对缓存进行“预热”,解决多个请求并发问题使用分布式锁和lua脚本保证共享数据的原子性访访问和修改。