}
在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流
缓存 缓存的目的是提升系统访问速度和增大系统处理容量降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再打开限流 限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理。后端代码:
//基于令牌桶算法的限流实现类,表示最大许可数是10 RateLimiter rateLimiter = RateLimiter.create(10); // 尝试获取一个许可,最多等待timeout时间 if (!rateLimiter.tryAcquire(1000, TimeUnit.MILLISECONDS)) { //没有令牌(请求超过令牌数后,没有获取到令牌) return Result.error(CodeMsg.ACCESS_LIMIT_REACHED); } //内存标记,减少redis访问 boolean over = localOverMap.get(goodsId); if (over) { return Result.error(CodeMsg.SECKILL_OVER); } //预减库存(将redis中的库存数减一,但数据库中的库存数未减少) long stock = redisService.decr(GoodsKey.getGoodsStock, "" + goodsId);//10 //当redis中的库存小于0的时候 if (stock < 0) { afterPropertiesSet(); //数据库中获取到的库存数减一 long stock2 = redisService.decr(GoodsKey.getGoodsStock, "" + goodsId);//10 if(stock2 < 0){ //库存数为0,则返回商品秒杀结束 localOverMap.put(goodsId, true); return Result.error(CodeMsg.SECKILL_OVER); } } @Override public void afterPropertiesSet() { //查询数据库 List<GoodsVo> goodsVoList = goodsService.listGoodsVo(); //没有这个商品 if (goodsVoList == null) { return; } for (GoodsVo goods : goodsVoList) { //将redis中的数量置为数据库中的库存数量 redisService.set(GoodsKey.getGoodsStock, "" + goods.getId(), goods.getStockCount()); //初始化商品都是没有处理过的 localOverMap.put(goods.getId(), false); } } //判断重复秒杀,如果redis中有相同的手机号与订单则为重复秒杀 SeckillOrder order = orderService.getOrderByUserIdGoodsId(user.getId(), goodsId); if (order != null) { return Result.error(CodeMsg.REPEATE_SECKILL); } //最后用RabbitMQ异步下单,最后才会访问数据库,这样做是为了最大力度减少对数据库的访问。 //入队 SeckillMessage message = new SeckillMessage(); message.setUser(user); message.setGoodsId(goodsId); sender.sendSeckillMessage(message); return Result.success(0);//排队中 @RabbitListener(queues=MQConfig.QUEUE) public void receive(String message){ log.info("receive message:"+message); SeckillMessage m = RedisService.stringToBean(message, SeckillMessage.class); User user = m.getUser(); long goodsId = m.getGoodsId(); GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsId); int stock = goodsVo.getStockCount(); if(stock <= 0){ return; } //判断重复秒杀 SeckillOrder order = orderService.getOrderByUserIdGoodsId(user.getId(), goodsId); if(order != null) { return; } //减库存 下订单 写入秒杀订单 seckillService.seckill(user, goodsVo); } //保证这三个操作,减库存 下订单 写入秒杀订单是一个事物 @Transactional public OrderInfo seckill(User user, GoodsVo goods){ //减库存 boolean success = goodsService.reduceStock(goods); if (success){ //下订单 写入秒杀订单 return orderService.createOrder(user, goods); }else { setGoodsOver(goods.getId()); return null; } } /** * 减少库存,每次减一 * * @return */ public boolean reduceStock(GoodsVo goods) { int numAttempts = 0; int ret = 0; SeckillGoods sg = new SeckillGoods(); sg.setGoodsId(goods.getId()); sg.setVersion(goods.getVersion()); do { numAttempts++; try { //获得乐观锁当前版本 sg.setVersion(goodsMapper.getVersionByGoodsId(goods.getId())); ret = goodsMapper.reduceStockByVersion(sg); } catch (Exception e) { e.printStackTrace(); } if (ret != 0) //update语句影响条数>0 break; //循环,如果请求大于五次结束,最后判断有没有更新数据库 } while (numAttempts < DEFAULT_MAX_RETRIES); return ret > 0; } //stock_count > 0 和 版本号实现乐观锁 防止超卖 @Update("update sk_goods_seckill set stock_count = stock_count - 1, version= version + 1 where goods_id = #{goodsId} and stock_count > 0 and version = #{version}") public int reduceStockByVersion(SeckillGoods seckillGoods) /** * 因为要同时分别在订单详情表和秒杀订单表都新增一条数据,所以要保证两个操作是一个事物 */ @Transactional public OrderInfo createOrder(User user, GoodsVo goods) { OrderInfo orderInfo = new OrderInfo(); orderInfo.setCreateDate(new Date()); orderInfo.setDeliveryAddrId(0L); orderInfo.setGoodsCount(1); orderInfo.setGoodsId(goods.getId()); orderInfo.setGoodsName(goods.getGoodsName()); orderInfo.setGoodsPrice(goods.getGoodsPrice()); orderInfo.setOrderChannel(1); orderInfo.setStatus(0); orderInfo.setUserId(user.getId()); orderMapper.insert(orderInfo); SeckillOrder seckillOrder = new SeckillOrder(); seckillOrder.setGoodsId(goods.getId()); seckillOrder.setOrderId(orderInfo.getId()); seckillOrder.setUserId(user.getId()); orderMapper.insertSeckillOrder(seckillOrder); redisService.set(OrderKey.getSeckillOrderByUidGid, "" + user.getId() + "_" + goods.getId(), seckillOrder); return orderInfo; }
