高并发秒杀问题(1) | 青训营笔记

82 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 12 天

本堂课重点内容

高并发下秒杀系统的相关问题(1)
1.页面静态化+CDN解决静态资源的页面资源的返回
2.缓存问题
3.库存问题

详细知识点介绍

页面静态化+CDN解决静态资源的页面资源的返回

对于秒杀界面的静态资源请求的返回,如果直接访问服务端,服务端会因为并发量过大而挂掉,所以可以使用CDN(Content Delivery Network,即内容分发网络),使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。

image.png

缓存问题

引入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;");

该代码的主要流程如下:

  1. 先判断商品id是否存在,如果不存在则直接返回。
  2. 获取该商品id的库存,判断库存如果是-1,则直接返回,表示不限制库存。
  3. 如果库存大于0,则扣减库存。
  4. 如果库存等于0,是直接返回,表示库存不足。

lua脚本判断和减库存原子性执行了,所以不会导致并发问题。

课后个人总结

高并发秒杀系统的设计需要考虑过滤不存在的请求,访问静态资源时不访问服务器,引入redis缓存来提高查询效率,启动之前对缓存进行“预热”,解决多个请求并发问题使用分布式锁和lua脚本保证共享数据的原子性访访问和修改。