只提供一种思路,可用作面试。需要自己结合代码去实践
商品秒杀
秒杀服务一般单独部署
秒杀商品存入缓存。利用redis原子性操作防止超卖以及高并发。
秒杀异步下单实现
- 将秒杀商品放入对应时间段的redis中。进行预处理。–防止超卖原子性操作
- 异步下单:预扣减缓存中的库存再异步(mq)扣除mysql数据
- 保证生产者发送的消息mq中不丢失:
- mq开启交换机、队列、消息的持久化(将mq的数据写入磁盘防止宕机)。
- 开启mq数据保护机制的confirm异步机制进行异步的消息确认(防止写入失败情况出现。生产者收到失败则继续发,成功则删除相应备份消息数据)。
- 生产者将发送的消息做一个备份(存在缓存或者mysql中即可),防止发送失败后重新发送。
-
保证消费消费消息不丢失:消费者改为手动应答模式。在处理完成业务逻辑(修改mysql数据)后根据执行结果进行手动给mq进行应答。
-
流量削峰:控制消费者接受消息总数(消费者预抓取总数控制)。
防止雪崩:例如消费者最多接受1w条,1s时mq给了1w个,2s时mq又给了1w个消息,消费者可能为处理完上1w个数据就会导致手动确认为失败,这时mq就会继续将未成功的消息发送,会导致消费者超出处理能力而系统宕机。
防止恶意刷单
根据用户名+id为key存储到redis,值为每次+1操作>1则判定为恶意刷单,返回即可。
/**
* 防止重复提交
* 根据用户名和商品id为key往redis中存储一个值,相同则值+1*/
private String preventRepeatCommit(String username,Long id) {
String redisKey = "seckill_user_" + username+"_id_"+id;
long count = redisTemplate.opsForValue().increment(redisKey, 1);
if (count == 1){
//设置有效期五分钟
redisTemplate.expire(redisKey, 5, TimeUnit.MINUTES);
return "success";
}
if (count>1){
return "fail";
}
return "fail";
}
防止相同商品重复秒杀
根据订单中用户名和商品id确定是否购买过该商品
下单接口隐藏
在前端请求中定义一个生成随机串的请求,作为包裹,在请求响应中请求真正的下单接口,对随机串进行校验一致则执行不一致则返回。
//秒杀下单
add:function(id){
app.msg ='正在下单';
//获取随机数
axios.get("/api/wseckillorder/getToken").then(function (response) {
var random=response.data;
axios.get("/api/wseckillorder/add?time="+app.dateMenus[0]+"&id="+id+"&random="+random).then(function (response) {
if (response.data.flag){
app.msg='抢单成功,即将进入支付!';
}else{
app.msg='抢单失败';
}
})
})
}
@RequestMapping("/add")
@ResponseBody
public Result add(String time, Long id) {
String coo = this.readCookie();
//下单接口的隐藏。校验密文有效
String randomcode = (String) redisTemplate.boundValueOps("randomcode_"+coo).get();
if (!randomcode.equals(randomcode)){
return new Result(false, StatusCode.ERROR,"无效访问");
}
Result result = secKillOrderFeign.add("2021092416", id);
return result;
}
/**
* 接口加密
* 生成随机数存入redis,10秒有效期
*/
@GetMapping("/getToken")
@ResponseBody
public String getToken() {
String randomString = RandomUtil.getRandomString();
//将cookie中的jti的值作为redis的key
String cookieValue = this.readCookie();
redisTemplate.boundValueOps("randomcode_" + cookieValue).set(randomString, 10, TimeUnit.SECONDS);
return randomString;
}
//读取cookie
private String readCookie() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String cookieValue = CookieUtil.readCookie(request, "uid").get("uid");
return cookieValue;
}