一、优化概述
对于秒杀系统的优化方式有很多很多,从这篇博文开始我们一步一步的对系统进行性能的优化,同时记录每一步优化的主要思路和相关的代码实现原理,并在每次优化后使用 jmeter 对系统进行压测。这篇博文主要使用 Redis 缓存、JVM 缓存和 MQ 异步执行任务来对系统进行优化。
项目源码 GitHub:https://github.com/TIYangFan/SpikeSystem(如果可以帮到你,请帮我 star ~ ^_^)
二、Redis 缓存优化
2.1 主要思想
使用 Redis 来进行优化,主要是因为 Redis 是存储在内存当中,所以数据的存取速度非常的快,同时因为 Redis 的自身的良好设计,单节点的 Redis 就能够支持上万的 QPS,这相比于数据库的几百 QPS 有了很大的提升。我们使用 Redis 来进行优化的主要思想是因为在秒杀的场景中,我们需要改变的数据属性很少,一般仅仅是改变商品的库存即可,但是因为我们之前的数据都是直接保存在数据库中,所以哪怕我们仅仅是改变一个属性值,都需要进行一次完整的数据库请求,这样在高并发的情况下,相当于把所以的流量直接全部压在数据库上,这样的做法很容易导致数据库直接挂掉,另一方面如果我们的数据库没有进行分库分表,那么这样的一个秒杀功能模块的请求可能一瞬间把数据库压垮后直接影响到其他功能模块的正常使用。
同时,因为相对于可能成千上万的访问量,商品的库存仅仅是十几个,这样来看两个数据相差了好几个数量级,也就是说其实真正有必要去进行数据库操作的仅仅是那十几个访问请求。因此我们在这里优化的主要思路就是将 Redis 作为一道缓存过滤( 因为在系统中对于 Redis 的访问会比 MySQL 快很多 ),即我们首先将数据库中的商品数据同步到 Redis 当中,当用户来访问的时候,我们直接去对 Redis 进行操作,一种情况是前库存数个人正常完成了 Redis 当中的减库存操作,那么他们可以直接进入到对数据库的减库存操作,另一种情况是当 Redis 当中的库存已经减光后,此时数据库中的商品库存也应被清空(最终一致性),那么这些后面的用户就直接在 Redis 缓存层被打回,这样就减少了对数据库的直接访问量。
通过下图和之前数据的对比,我们可以看到优化后的 TPS 大概为1400 左右,相比于优化前的 550 大概有了一倍的提升( 因为 Redis 可支持的 QPS 量比较大,所以这里直接采用了比较大的访问量,这样看起来比较直观,但是因为我没有进行 Tomcat 等等一些参数的配置,所以很多的请求直接被 Tomcat 挂掉了,所以失误率比较高,问题不大,当把访问量调小的时候或者配置好 Tomcat 的相关参数后就没问题了 )。
2.2 优化后效果
2.3 优化代码
@Autowired
private StringRedisTemplate stringRedisTemplate;
@PostConstruct
public void initRedis() {
// 将数据库中所有商品的库存信息同步到 Redis 中
List<Product> products = productService.getAllProduct();
for (Product product: products) {
stringRedisTemplate.opsForValue().set(Constants.PRODUCT_STOCK_PREFIX + product.getId(), product.getStock() + "");
}
}
@PostMapping("/{productId}")
public String spike(@PathVariable("productId") Long productId) {
try {
// Redis 缓存中减库存
Long stock = stringRedisTemplate.opsForValue().decrement(Constants.PRODUCT_STOCK_PREFIX + productId);
// 如果 Redis 当中的库存已被减完则直接打回
if (stock < 0) {
return "fail";
}
// 数据库中减库存
orderService.spike(productId);