赞
踩
在结束之际,我想重申的是,学习并非如攀登险峻高峰,而是如滴水穿石般的持久累积。尤其当我们步入工作岗位之后,持之以恒的学习变得愈发不易,如同在茫茫大海中独自划舟,稍有松懈便可能被巨浪吞噬。然而,对于我们程序员而言,学习是生存之本,是我们在激烈市场竞争中立于不败之地的关键。一旦停止学习,我们便如同逆水行舟,不进则退,终将被时代的洪流所淘汰。因此,不断汲取新知识,不仅是对自己的提升,更是对自己的一份珍贵投资。让我们不断磨砺自己,与时代共同进步,书写属于我们的辉煌篇章。
需要完整版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点)
**网关:①.**限流 **②.**用户黑名单和白名单拦截
后台:
服务降级,隔离,熔断(Hystrix)
从redis中获取秒杀的令牌(能够获取到令牌就能够秒杀成功,否则就秒杀失败)
3. 异步使用MQ执行修改库存,提交订单记录操作
4. 提供一个根据用户信 息查询秒杀结果接口(对应前端第③点)
拓展:
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。