秒杀总结:
前端:页面分为静态的html部分和参与秒杀的web接口
通常静态页面html通过cdn部署,所以压力不大,一般是我们后台请求接口的瓶颈。
后端:
过滤请求:
同一个用户发送多次请求
- 方案一:redis缓存这个信息,保证请求一次
- 方案二:将同一个账号的信息放入一个队列里,一个一个处理
多个用户多个请求:
-
方案一:用机器检测ip频率,如果频率很高,谈验证码或者禁用ip(同一个ip)
-
方案二:如果发现账号相连或者活跃度不高,没有资格,也可以通过风控,就是通过数据挖掘查看黄牛党的相似特征(不同ip)
请求处理:
两个需要注意的点:高并发,超卖
高并发
使用rabbitmq进行消峰。controller生产者
@RequestMapping("/seckill/manage")
public SysResult startSeckill(Order order){
//查看商品是否开始售卖
String saleStatusKey = env.getProperty("seckill.redis.key.prefix") + order.getGoodsId();
// 1代表售卖中,0代表未开始,-1代表已结束
String saleStatus = redisTemplate.opsForValue().get(saleStatusKey));
if(!"1".equal()){
return "0".equal(saleStatus) ? SysResult.error(9001,"活动未开始"):SysResult.error(9002,"活动已结束");
}
//如果只允许一个用户秒杀一次 可以使用redis
String key = env.getProperty("seckill.redis.key.prefix") + order.getUserId() + order.getGoodsId();
// 用分布式锁查看用户是否已经抢购,如果是限时抢购,需要加上失效时间
boolean checkSeckillUser = redisTemplate.opsForValue().setIfAbsent(key,"1");
if(!checkSeckillUser){
return SysResult.error(9005,"您已经参加过了");
}
//String msg=userId+"/"+seckillId;
// 把消息发送给订单消息队列
//设置交换机
rabbitTemplate.setExchange(env.getProperty("order.mq.exchange.name"));
//设置routingkey
rabbitTemplate.setRoutingKey(env.getProperty("order.mq.routing.key"));
Message message = MessageBuilder.withBody(order).setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("UTF-8").setMessageId(UUID.randomUUID().toString()).build();
rabbitTemplate.convertAndSend
("seckillEX",
"seckill",message);
//等待声明代码配置完毕再验证功能
// Todo
return SysResult.ok();
}
/**
* 消息监听器(消费者)
*/
@Component
public class OrderListener implements ChannelAwareMessageListener {
@Autowired
private OrderService orderService;
/**
* 处理接收到的消息
* @param message 消息体
* @param channel 通道,确认消费用
* @throws Exception
*/
@Override
public void onMessage(Message message, Channel channel) throws Exception {
try{
//下单,操作数据库
orderService.order(obj.getString("userId"),obj.getString("goodsId"));
//确认消费
channel.basicAck(tag,true);
}catch(Exception e){
logger.error("消息监听确认机制发生异常:",e.fillInStackTrace());
}
}
}