在前一篇文章《秒杀系统实战系列(十二)查询库存介绍之使用redis缓存》我们介绍了使用redis做缓存查询库存,虽然所有的链接都不用打到mysql了,但是所有的连接缺都打到redis上了,像这两天是双11期间,很多很多的商品进行秒杀,此时所有的请求都打到redis上,redis也扛不住啊?那怎么办?其实这里就是再在本地添加一个二级缓存,这样子可以缓解一些redis的压力。本文我们就来演示下使用guava框架在本地做二级缓存。
一、首先引入guava缓存依赖
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>18.0</version> </dependency>
二、编写一个公共的guavamanager
这里用guavamanager主要是为了统一惯例缓存,并且实现初始化的一些信息,示例代码如下:
package org.common.manager.redis; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; import org.springframework.stereotype.Component; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; @Component public class GuavaManager { private Cache<String, String> localCache; @PostConstruct public void init() { localCache = CacheBuilder.newBuilder() .initialCapacity(1024) .maximumSize(1024) .concurrencyLevel(3) //3秒超时 .expireAfterWrite(3, TimeUnit.SECONDS).build(); } public void put(String key, String value) { localCache.put(key, value); } public String get(String key) { return localCache.getIfPresent(key); } }
三、在秒杀代码里面使用GuavaManager
接着我们就可以在秒杀的service方法里面使用guavaManager了,示例代码如下:
@Override public BaseResponse doSeckill(String userNo, Long goodId) { BaseResponse res = null; GoodsSeckillDto seckillInfo = null; Long currentTime = new Date().getTime(); // 首先从guava缓存获取 String cacheSeckillGood = guavaManager.get(RedisPrefix.GOODSTOCKCACHE + goodId); // 如果guava缓存获取不到,那么肯定是有提示了,因为我们是在redis有提示的时候才设置这个缓存 if (!StringUtils.isEmpty(cacheSeckillGood)) { log.info("直接从guava获取缓存"); seckillInfo = JSON.parseObject(cacheSeckillGood, GoodsSeckillDto.class); // 判断是否在秒杀时间段内 if (!(currentTime < seckillInfo.getEndDate().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()) && currentTime > seckillInfo.getStartDate().atZone(ZoneId.systemDefault()).toInstant() .toEpochMilli()) { return BaseResponse.fail(HttpStatusCode.GOODNOTSECKILLTIME); } // 判断库存 if (seckillInfo.getStockCount() < 1) { return BaseResponse.fail(HttpStatusCode.GOODSTOCKNOTENOUGH); } } else { log.info("进入到redis缓存获取"); // 从redis中获取 cacheSeckillGood = redisManagaer.getHashValue(RedisPrefix.GOODSTOCKCACHE + goodId); // 如果redis获取到了,则进行判断 if (!StringUtils.isEmpty(cacheSeckillGood)) { seckillInfo = JSON.parseObject(cacheSeckillGood, GoodsSeckillDto.class); // 判断是否在秒杀时间段内 if (!(currentTime < seckillInfo.getEndDate().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()) && currentTime > seckillInfo.getStartDate().atZone(ZoneId.systemDefault()).toInstant() .toEpochMilli()) { //这里如果报错,我们把错误信息存放到guawa中,下一次直接从guava中进行判断 guavaManager.put(RedisPrefix.GOODSTOCKCACHE + goodId, cacheSeckillGood); return BaseResponse.fail(HttpStatusCode.GOODNOTSECKILLTIME); } // 判断库存 if (seckillInfo.getStockCount() < 1) { //这里如果报错,我们把错误信息存放到guawa中,下一次直接从guava中进行判断 guavaManager.put(RedisPrefix.GOODSTOCKCACHE + goodId, cacheSeckillGood); return BaseResponse.fail(HttpStatusCode.GOODSTOCKNOTENOUGH); } } } // 无论有没有缓存的判断,我们走到这一步也要去查询一次数据库再进行一次判断 res = goodsClient.getGoodsSeckillInfo(GoodRequest.builder().goodId(goodId).build()); if (res.isOk()) { String info = JSON.toJSONString(res.getData()); seckillInfo = JSON.parseObject(info, GoodsSeckillDto.class); } if (null == seckillInfo) { return BaseResponse.fail(HttpStatusCode.GOODNOTSECKILL); } // 判断当前商品当前用户是否已经秒杀成功过了,如果有秒杀过,则返回不能再抢。这里由于我们一会要做并发测试,因此这里我们暂时不做此判断。 // 判断当前时间段是不是秒杀时间段 if (currentTime < seckillInfo.getStartDate().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()) { if (!redisManagaer.existHashValue(RedisPrefix.GOODSTOCKCACHE + goodId)) { redisManagaer.setHashValue(RedisPrefix.GOODSTOCKCACHE + goodId, JSON.toJSONString(seckillInfo), seckillInfo.getStartDate().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - currentTime, TimeUnit.MILLISECONDS); log.info("写入秒杀不在时间段内的缓存"); } log.info("返回秒杀不在时间段内"); return BaseResponse.fail(HttpStatusCode.GOODNOTSECKILLTIME); } if (currentTime > seckillInfo.getEndDate().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()) { if (!redisManagaer.existHashValue(RedisPrefix.GOODSTOCKCACHE + goodId)) { redisManagaer.setHashValue(RedisPrefix.GOODSTOCKCACHE + goodId, JSON.toJSONString(seckillInfo), 7L, TimeUnit.DAYS); log.info("写入秒杀不在时间段内的缓存"); } log.info("返回秒杀不在时间段内"); return BaseResponse.fail(HttpStatusCode.GOODNOTSECKILLTIME); } // 判断库存 if (seckillInfo.getStockCount() < 1) { if (!redisManagaer.existHashValue(RedisPrefix.GOODSTOCKCACHE + goodId)) { redisManagaer.setHashValue(RedisPrefix.GOODSTOCKCACHE + goodId, JSON.toJSONString(seckillInfo), 7L, TimeUnit.DAYS); log.info("写入秒杀库存不足的缓存"); } log.info("返回秒杀库存不足"); return BaseResponse.fail(HttpStatusCode.GOODSTOCKNOTENOUGH); } // 开始进入秒杀 return submitSeckillAction(userNo, goodId); }
然后我们启动下项目可以看到第一次请求进入redis查询缓存了,第二次请求直接走的guava缓存:
以上就是使用guava缓存的案例。
备注:
1、这里的guava缓存主要应用于阻挡一些错误信息,因此我们是在redis出现报错的时候才写guava。 2、guava缓存可以有效的减少redis的缓存请求。 3、这里的guava不能实现给key设置值的时候设置过期时间,但是我们代码里面有对对应的数据进行判断,因此这里如果guava的缓存时间是2023年11月10日10点57分5秒,但是秒杀开始时间是2023年11月10日10点57分4秒,此时经过各种if-else的判断,其实还是能秒杀成功的(这块没有测试过,大家可以测试下源代码)
最后附上本案例的源码,登陆后即可下载。
还没有评论,来说两句吧...