网络安全最新微服务高并发秒杀实战_基于令牌桶实现库存,2024年最新行业寒冬

写在最后

在结束之际,我想重申的是,学习并非如攀登险峻高峰,而是如滴水穿石般的持久累积。尤其当我们步入工作岗位之后,持之以恒的学习变得愈发不易,如同在茫茫大海中独自划舟,稍有松懈便可能被巨浪吞噬。然而,对于我们程序员而言,学习是生存之本,是我们在激烈市场竞争中立于不败之地的关键。一旦停止学习,我们便如同逆水行舟,不进则退,终将被时代的洪流所淘汰。因此,不断汲取新知识,不仅是对自己的提升,更是对自己的一份珍贵投资。让我们不断磨砺自己,与时代共同进步,书写属于我们的辉煌篇章。

需要完整版PDF学习资源私我

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以点击这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

① 编写生成100个令牌桶接口

    

② 编写获取token代码,并用mq异步发送

RabbitMQ消费者

@Component
Slf4j
public class StockConsumer {
    @Autowired
    private SeckillMapper seckillMapper;
    @Autowired
    private OrderMapper orderMapper;
    @RabbitListener(queues = "modify_inventory_queue")
    @Transactional
    public void process(Message message, @Headers Map<String, Object> headers, Channel channel) throws IOException {
	String messageId = message.getMessageProperties().getMessageId();
	String msg = new String(message.getBody(), "UTF-8");
	JSONObject jsonObject = JSONObject.parseObject(msg);
	// 1.获取秒杀id
	Long goodsId = jsonObject.getLong("seckillId");
	SeckillEntity seckillEntity = seckillMapper.findBySeckillId(goodsId);
	if (seckillEntity == null) {
	    log.warn("goodsId:{},商品信息不存在!", goodsId);
	    return;
	}
	Long version = seckillEntity.getVersion();
        // 2.减库存
	int inventoryDeduction = seckillMapper.inventoryDeduction(goodsId, version);
	if (!toDaoResult(inventoryDeduction)) {
	    log.info(">>>seckillId:{}修改库存失败", goodsId);
	    return;
	}
	// 3.添加订单
	OrderEntity orderEntity = new OrderEntity();
	String phone = jsonObject.getString("phone");
	orderEntity.setUserPhone(phone);
	orderEntity.setSeckillId(goodsId);
	orderEntity.setState(1l);
	int insertOrder = orderMapper.insertOrder(orderEntity);
	if (!toDaoResult(insertOrder)) {
	    return;
	}
	log.info(">>>成功消费seckillId:{},秒杀成功!", goodsId);
    }
    // 调用数据库层判断
    public Boolean toDaoResult(int result) {
        return result > 0 ? true : false;
    }
}

③  提供一个根据用户信息查询秒杀结果接口(实际开发中,也可以根据userId)

@GetMapping("/checkSpike")
public BaseResponse<JSONObject> getOrder(String phone, Long goodsId) {
    if (StringUtils.isEmpty(phone)) {
	return setResultError("手机号码不能为空!");
    }
    if (goodsId== null) {
    	return setResultError("商品库存id不能为空!");
    }
    OrderEntity orderEntity = orderMapper.findByOrder(phone, goodsId);
    if (orderEntity == null) {
    	return setResultError("正在排队中.....");// 要么还没被mq消费,要么没抢到token令牌
    }
    return setResultSuccess("恭喜你秒杀成功!");
}
public interface OrderMapper {
    @Select("SELECT goods_id,user_phone,stateFROM order_table WHERE USER_PHONE=#{phone} and goods_id=#{goodsId} AND STATE=1")
    OrderEntity findByOrder(@Param("phone")String phone, @Param("goodsId")Long goodsId);
}

前端需要写一个定时器,用于查询秒杀成功状态:

前端调用秒杀接口spike,如果秒杀成功的话,返回正在排队中。。。

前端写一个定时器调用checkSpike接口,使用手机号/userId + 商品id查询是否秒杀成功。

(如果调用spike返回售罄,则前端不用写定时器)

    

三. 网关优化

1. 基于Google的guava实现限流(基于令牌桶)

令牌桶实现原理: 以规定的速率往令牌桶中存入Token,用户请求必须获取到令牌中的Token才可以处理 请求,如果没有从令牌桶中获取到令牌则丢失该请求。 例如:令牌桶中最多只能存放50个Token,以规定速率存入Token实现在高并发情况下限流 。

Google的Guava工具包中就提供了一个限流工具类——RateLimiter,本文也是通过使用该工具类来实现限流功能。RateLimiter是基于“令牌通算法”来实现限流的,具体步骤如下:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
</dependency>

在zuul网关过滤器的run方法里,加入以下代码:

@Component
@Slf4j
public class GatewayFilter extends ZuulFilter {
    // 每秒存入令牌中token数为1
    private static final RateLimiter rateLimiter = RateLimiter.create(1);
    public Object run() throws ZuulException {
        /** 这里省略一系列的验证token,黑名单白名单等... */
        /* 
            默认超时时间是0,意思是拿不到就立即返回false,如果想修改超时时间,采用该代码:
            boolean tryAcquire = rateLimiter.tryAcquire(0, TimeUnit.SECONDS);
        */
        boolean tryrateLimiter.tryAcquire(); //阻塞等待超时时间,这里设为0,如果没有拿到令牌,直接拒绝访问,无需等待      
	    if (!tryAcquire) {// 返回false,表示没有获取到令牌,直接return
	        resultError(500, ctx, "现在抢购的人数过多,请稍等一下下哦!");
	        return;
	    }
        // 否则,获取到令牌,放行,继续执行后续逻辑,待所有过滤都通过,则直接访问秒杀接口...
   }
}

2. 使用Hystrix实现服务线程池隔离

默认情况下,上面的秒杀接口spike和查询秒杀结果接口checkSpike,都在一个线程池;在高并发场景,秒杀接口的压力会非常大,当一秒内用户全部请求秒杀接口,线程池都去处理秒杀接口,没有空闲线程去处理查询秒杀结果接口,这时候会产生延迟等待问题(默认tomcat只有一个线程池去处理所有请求,一旦线程池满了,导致其他线程无法访问)。

同时请求spike和checkSpike接口,打印日志如下:

>>>>>秒杀接口线程池名称:http-nio-9800-exec-1
>>>>>查询秒杀结果线程名称:http-nio-9800-exec-2

可以看到,两个接口处于同一个线程池,下面引入Hystrix实现服务降级,隔离:

<!-- 引入hystrix依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

启动类加入该注解:@EnableHystrix

秒杀接口,加入hystrix实现服务降级,线程池隔离

@HystrixCommand(fallbackMethod = "spikeFallback")
public BaseResponse<JSONObject> spike(String phone, Long seckillId) {
    // 上面spike里面的逻辑...
}
private BaseResponse<JSONObject> spikeFallback(String phone, Long seckillId) {
	return setResultError("服务器忙,请稍后重试!");
}

再次请求spike和checkSpike接口,打印日志如下:

>>>>>秒杀接口线程池名称:hystrix-SpikeApiImpl-1
>>>>>查询秒杀结果线程名称:http-nio-9800-exec-3

可以看到,两个接口的线程池是不同的;网络延迟的情况下,防止用户一直等待,会走服务降级方法spikeFallback,返回一个友好提示!

秒杀思路总结

**前端:①.**动静分离  **②.**防止表单重复提交  **③.**秒杀详情页面,使用定时器根据用户信息查询(对应后台第4点)

**网关:①.**限流  **②.**用户黑名单和白名单拦截

后台:

  1. 服务降级,隔离,熔断(Hystrix)

  2. 从redis中获取秒杀的令牌(能够获取到令牌就能够秒杀成功,否则就秒杀失败)

3. 异步使用MQ执行修改库存,提交订单记录操作

4. 提供一个根据用户信 息查询秒杀结果接口(对应前端第③点)

拓展:

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以点击这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值