地下城显示服务器削峰什么意思,第九章 流量削峰技术

问题1:下单的请求可以通过脚本不停的刷造成黄牛还有对服务器的压力

可以在秒杀令牌颁发的过程中做限购 比如一个用户只能拿一个令牌等逻辑

问题2:秒杀下单逻辑和秒杀下单接口写在一起,强冗余。即使活动不开始,也可以作为普通商品下单。会对交易系统造成无关联负载

解决:引入秒杀令牌,将秒杀下单逻辑放到生成令牌这里,这样方便以后分开部署。

1.使用令牌来避免大量的访问来下单

秒杀令牌来管风控和验证,避免大流量的用户来进行下单操作

生成令牌一般比库存多一些,例如两倍

先调用/generatePromoToken, 生成promoToken,然后携带promoToken去下单/createorder

(1)生成秒杀令牌

public String generateSecondKillTocken(Integer itemId, Integer userId, Integer promoId, Integer amount) {

//1.校验下单状态,下单的商品是否存在,用户是否合法,购买数量是否正确

ItemModel itemModel = itemService.getItemByIdInCache(itemId);

if(itemModel == null){

return null;

}

UserModel userModel = userService.getUserByIdInCache(userId);

if(userModel == null){

return null;

}

if(amount <= 0 || amount > 99){

return null;

}

//校验活动信息

if(promoId != null){

//(1)校验对应活动是否存在这个适用商品

if(promoId.intValue() != itemModel.getPromoModel().getId()){

return null;

//(2)校验活动是否正在进行中

}else if(itemModel.getPromoModel().getStatus().intValue() != 2) {

return null;

}

}

// 生成抢购token,并存入reids

String token = UUID.randomUUID().toString().replace("-", "");

redisTemplate.opsForValue().set("promo_token_"+promoId+"_"+itemId+"_"+userId, token);

redisTemplate.expire("promo_token_"+promoId+"_"+itemId+"_"+userId, 5, TimeUnit.MINUTES);

return token;

}

(2)下单验证令牌

//封装下单请求

@RequestMapping(value = "/createorder",method = {RequestMethod.POST},consumes={CONTENT_TYPE_FORMED})

@ResponseBody

public CommonReturnType createOrder(@RequestParam(name="itemId")Integer itemId,

@RequestParam(name="amount")Integer amount,

@RequestParam(name="promoId",required = false)Integer promoId,

@RequestParam(name="token",required = false)String token,

@RequestParam(name="promoToken",required = false)String promoToken) throws BusinessException {

UserModel userModel = (UserModel) redisTemplate.opsForValue().get(token);

// 秒杀令牌校验,与redis中的值比较

if(promoId!=null) {

if(promoToken!=null) {

String inRedisPromoToken = (String) redisTemplate.opsForValue().get("promo_token_"+promoId+"_"+itemId+"_"+userModel.getId());

if(!promoToken.equals(inRedisPromoToken)) {

throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "秒杀令牌验证失败");

}

} else {

throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "秒杀令牌验证失败");

}

}

// 添加商品库存流水init状态

String stockLogId = itemService.initStockLog(itemId, amount);

//OrderModel orderModel = orderService.createOrder(userModel.getId(),itemId,promoId,amount);

// 事务型的消息驱动下单,同时根据回调状态来决定发送还是回滚消息

boolean mqResult = mqProducer.transactionAsyncReduceStock(userModel.getId(),itemId,promoId,amount, stockLogId);

if(!mqResult) {

throw new BusinessException(EmBusinessError.UNKNOWN_ERROR,"下单失败");

}

return CommonReturnType.create(null);

}

问题3:秒杀令牌的实现有缺陷,可以无限制的生成,这样如果有一亿用户过来,生成影响系统性能,而且一个令牌也不能抢到商品

解决:引入秒杀大闸,根据库存来颁发对应的数量的令牌,控制大闸流量

(1)在发布活动时,库存保存到redis中时,将大闸数量也保存到redis

public void publishPromo(Integer promoId) {

。。。。。。。。。。。。。。。。。

//将库存同步到redis中

redisTemplate.opsForValue().getAndSet("promo_item_stock_"+promoDO.getItemId(), itemModel.getStock());

//将秒杀大闸数量保存到redis

redisTemplate.opsForValue().set("promo_door_count_"+promoId, itemModel.getStock().intValue()*5);

}

(2)生成令牌前先校验秒杀大闸数量是否还有

@Override

public String generateSecondKillTocken(Integer itemId, Integer userId, Integer promoId, Integer amount) {

// 校验库存是否售罄

if(redisTemplate.hasKey("promo_item_stock_invalid_"+itemId)) {

return null;

}

//1.校验下单状态,下单的商品是否存在,用户是否合法,购买数量是否正确

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

// 获取秒杀大闸数量long result = redisTemplate.opsForValue().increment("promo_door_count_"+promoId, -1);

if(result<0) {

return null;

}

// 生成抢购token,并存入reids

String token = UUID.randomUUID().toString().replace("-", "");

redisTemplate.opsForValue().set("promo_token_"+promoId+"_"+itemId+"_"+userId, token);

redisTemplate.expire("promo_token_"+promoId+"_"+itemId+"_"+userId, 5, TimeUnit.MINUTES);

return token;

}

问题4:令牌对浪涌流量的涌入无法应对,比如库存本身就非常大。另外多库存,多商品的令牌限制能力弱

解决:引入队列泄洪,将任务提交给线程池,线程池中可执行线程沾满后会将任务放到等待队列中,这样做就等于是限制了用户并发的流量,使得其在线程池的等待队列中排队处理。然后future的使用是为了让前端用户在调用controller后可以同步的获得执行的结果

1排队有时比并发更快,如果出现锁的等待,线程会退出。CPU调度另一个线程,CPU耗损上下文切换

比如:redis是单线程的,但是很快。因为redis是内存操作,且单线程上没有线程切换开销

private ExecutorService executorService;

@PostConstruct

public void init() {

executorService = Executors.newFixedThreadPool(20);

}

//封装下单请求

@RequestMapping(value = "/createorder",method = {RequestMethod.POST},consumes={CONTENT_TYPE_FORMED})

@ResponseBody

public CommonReturnType createOrder(@RequestParam(name="itemId")Integer itemId,

@RequestParam(name="amount")Integer amount,

@RequestParam(name="promoId",required = false)Integer promoId,

@RequestParam(name="token",required = false)String token,

@RequestParam(name="promoToken",required = false)String promoToken) throws BusinessException {

。。。。。。。。。。。。。。。。。。。。。。。。。。。。

// 同步调用线程池的submit方法

// 拥塞窗口为20的等待队列,用队列来泄洪,超过20的队列要等待

Future future = executorService.submit(new Callable() {

@Override

public Object call() throws Exception {

// 添加商品库存流水init状态

String stockLogId = itemService.initStockLog(itemId, amount);

//OrderModel orderModel = orderService.createOrder(userModel.getId(),itemId,promoId,amount);

// 事务型的消息驱动下单,同时根据回调状态来决定发送还是回滚消息

boolean mqResult = mqProducer.transactionAsyncReduceStock(userModel.getId(),itemId,promoId,amount, stockLogId);

if(!mqResult) {

throw new BusinessException(EmBusinessError.UNKNOWN_ERROR,"下单失败");

}

return null;

}

});

try {

// get方法获取执行结果,该方法会阻塞直到任务返回结果。

future.get();

} catch (InterruptedException | ExecutionException e) {

throw new BusinessException(EmBusinessError.UNKNOWN_ERROR);

}

return CommonReturnType.create(null);

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值