在前面的文章《秒杀系统实战系列(十一)多服务之间调用的分布式事务》其实我们已经实现了对于秒杀场景的同步秒杀方法,但是还是有一些缺陷,我们从这篇文章开始挨个完善下。
首先我们介绍第一个缺陷:查询库存
这里查询库存的时候,我们会发现是每次都从数据库进行查询最新的库存,然后进行判断的,试想一下,这是一个秒杀的场景,如果每次进来一个请求我们就去查询一次数据库,那么数据库的压力是不是很大?是不是很容易就被拖死了?那么怎么办呢?我们常做的做法是去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类型来接收。
接着我们把服务器启动起来,测试的时候就会发现可以直接走缓存了。
以上就是利用缓存在走判断逻辑,减少数据库访问的案例。最后按照惯例,附上本案例的源码,登陆后即可下载。
还没有评论,来说两句吧...