在前面的文章《秒杀系统实战系列(十一)多服务之间调用的分布式事务》其实我们已经实现了对于秒杀场景的同步秒杀方法,但是还是有一些缺陷,我们从这篇文章开始挨个完善下。
首先我们介绍第一个缺陷:查询库存
这里查询库存的时候,我们会发现是每次都从数据库进行查询最新的库存,然后进行判断的,试想一下,这是一个秒杀的场景,如果每次进来一个请求我们就去查询一次数据库,那么数据库的压力是不是很大?是不是很容易就被拖死了?那么怎么办呢?我们常做的做法是去redis缓存里面查询库存等信息就可以了,所以我们改进下order-service的servcice秒杀方法实现,示例代码如下:
@Override
public BaseResponse doSeckill(String userNo, Long goodId) {
String cacheSeckillGood = redisManagaer.getHashValue(RedisPrefix.GOODSTOCKCACHE + goodId);
BaseResponse res = null;
GoodsSeckillDto seckillInfo = null;
Long currentTime = new Date().getTime();
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()) {
return BaseResponse.fail(HttpStatusCode.GOODNOTSECKILLTIME);
}
// 判断库存
if (seckillInfo.getStockCount() < 1) {
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);
}
@HmilyTCC(confirmMethod = "doSeckillConfirmMethod", cancelMethod = "doSeckillCancelMethod")
private BaseResponse submitSeckillAction(String userNo, Long goodId) {
// 首先我们判断下商品状态 这里略过,因为我们这里没有涉及到商品是否销售贺不销售等情况。
// 接着就可以下单了。 减库存->为用户增加未订单
Boolean success = goodsClient.reduceGoodStock(GoodRequest.builder().goodId(goodId).build());
if (!success) {
return BaseResponse.fail(HttpStatusCode.GOODSTOCKNOTENOUGH);
}
// 获取用户信息
BaseResponse response = userClient.getUserInfoByNos(UserRequest.builder().userNos(userNo).build());
if (!response.isOk()) {
return BaseResponse.fail(HttpStatusCode.ORDERFAILS);
}
// 创建订单
SeckillOrderDto newOrder = SeckillOrderDto.builder().userNo(userNo).orderId(IdWorker.getId()).goodsId(goodId)
.build();
Boolean suc = odersManager.createNewSeckillOrder(newOrder);
return suc ? BaseResponse.ok() : BaseResponse.fail(HttpStatusCode.ORDERFAILS);
}这里我们的改进点有如下几个:
1、每次进来之后,先查询缓存,如果缓存没有就查询数据库 2、当库存不足或者不在秒杀时间段之内的话,我们就写缓存,如果其他情况就不写缓存。 3、这里我们把判断和真实下单的方法拆开了,真实下单才用分布式事务,这是上一篇文章留的作业,主要是因为如果判断在分布式事务里面,那么如果判断的出现提示,其实没有执行sql,但是他也会进入到confirm方法里面,这就没有任何意义了。 4、这里我们把mysql的datetime类型修改了一下,使用java的LocalDateTime类型来接收。
接着我们把服务器启动起来,测试的时候就会发现可以直接走缓存了。
以上就是利用缓存在走判断逻辑,减少数据库访问的案例。最后按照惯例,附上本案例的源码,登陆后即可下载。









还没有评论,来说两句吧...