当前位置:网站首页>秒杀系统的设计与实现思路
秒杀系统的设计与实现思路
2022-07-05 22:52:00 【秃了也弱了。】
文章目录
写在前面
秒杀大家都不陌生,而且是电商项目必备的一个技能点。
但是真正的秒杀服务是非常复杂的,秒杀具有瞬间高并发的特点,所以解决瞬间高并发的问题,就可以解决秒杀的问题。
今天就将秒杀系统完整的实现分解开,一起研究一下吧。
(有问题还请指正)
秒杀系统注意事项
服务单一职责+独立部署
秒杀服务是有很大风险的,一不小心就会造成服务宕机或者一瞬间占用大量服务器资源,所以秒杀服务必须独立部署,而且秒杀服务只做秒杀功能。
秒杀链接加密
防止恶意攻击,防止有人模拟秒杀请求造成服务器更大的压力;
防止链接暴露,防止工作人员提前秒杀商品。
库存预热+快速扣减
秒杀系统读多写少,我们可以先将库存总数预热,存入redis中,使用信号量来控制秒杀请求的数量。
动静分离
使用nginx做好动静分离,保证静态资源直接能够请求到,避免占用后端资源。(现在基本都是前后端分离项目,此处可忽略)
恶意请求拦截
识别非法攻击的请求进行拦截,可以从网关层拦截,判断用户是否登录。
流量错峰
使用各种手段,将流量分担到更大宽度的时间点。比如验证码、加入购物车,多加几步操作。
限流&熔断&降级
前端限流+后端限流。
限制每秒钟只能点击一次;限制总量;
后端快速失败、降级运行、熔断机制防止雪崩。
队列削峰
秒杀成功的所有商品,放入消息队列中,然后消费端慢慢创建订单等等逻辑。
具体实现
限流熔断降级
使用sentinel进行限流
详解sentinel:分布式系统的流量防卫兵
队列削峰
使用rockerMQ或者rabbitMQ进行削峰。
库存预热
使用定时任务,提前将商品信息、商品随机码(防止恶意攻击)、商品库存等信息存入redis。
伪代码:
/** * 缓存秒杀活动所关联的商品信息 */
private void saveProductInfo(List<Product> products) {
products.stream().forEach(products-> {
//准备hash操作,绑定hash
BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(SECKILL_CHARE_PREFIX);
products.getRelationSkus().stream().forEach(seckillSkuVo -> {
//生成随机码
String token = UUID.randomUUID().toString().replace("-", "");
String redisKey = seckillSkuVo.getPromotionSessionId().toString() + "-" + seckillSkuVo.getSkuId().toString();
if (!operations.hasKey(redisKey)) {
// 防止重复添加
//缓存我们商品信息
SeckillSkuRedisTo redisTo = new SeckillSkuRedisTo();
Long skuId = seckillSkuVo.getSkuId();
//1、先查询商品的基本信息,调用远程服务
R info = productFeignService.getSkuInfo(skuId);
if (info.getCode() == 0) {
SkuInfoVo skuInfo = info.getData("skuInfo",new TypeReference<SkuInfoVo>(){
});
redisTo.setSkuInfo(skuInfo);
}
//2、sku的秒杀信息
BeanUtils.copyProperties(seckillSkuVo,redisTo);
//3、设置当前商品的秒杀时间信息
redisTo.setStartTime(session.getStartTime().getTime());
redisTo.setEndTime(session.getEndTime().getTime());
//4、设置商品的随机码(防止恶意攻击)
redisTo.setRandomCode(token);
//序列化json格式存入Redis中
String seckillValue = JSON.toJSONString(redisTo);
operations.put(seckillSkuVo.getPromotionSessionId().toString() + "-" + seckillSkuVo.getSkuId().toString(),seckillValue);
//如果当前这个场次的商品库存信息已经上架就不需要上架
//5、使用库存作为分布式Redisson信号量(限流)
// 使用库存作为分布式信号量
RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
// 商品可以秒杀的数量作为信号量
semaphore.trySetPermits(seckillSkuVo.getSeckillCount());
}
});
});
}
秒杀实现
/** * 商品进行秒杀(秒杀开始) * @param killId * @param key * @param num * @return */
@GetMapping(value = "/kill")
public String seckill(@RequestParam("killId") String killId,
@RequestParam("key") String key,
@RequestParam("num") Integer num,
Model model) {
String orderSn = null;
try {
//1、判断是否登录
orderSn = seckillService.kill(killId,key,num);
model.addAttribute("orderSn",orderSn);
} catch (Exception e) {
e.printStackTrace();
}
return "success";
}
/** * 当前商品进行秒杀(秒杀开始) * @param killId * @param key * @param num * @return */
@Override
public String kill(String killId, String key, Integer num) throws InterruptedException {
long s1 = System.currentTimeMillis();
//获取当前用户的信息
MemberResponseVo user = LoginUserInterceptor.loginUser.get();
//1、获取当前秒杀商品的详细信息从Redis中获取
BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SECKILL_CHARE_PREFIX);
String skuInfoValue = hashOps.get(killId);
if (StringUtils.isEmpty(skuInfoValue)) {
return null;
}
//(合法性效验)
SeckillSkuRedisTo redisTo = JSON.parseObject(skuInfoValue, SeckillSkuRedisTo.class);
Long startTime = redisTo.getStartTime();
Long endTime = redisTo.getEndTime();
long currentTime = System.currentTimeMillis();
//判断当前这个秒杀请求是否在活动时间区间内(效验时间的合法性)
if (currentTime >= startTime && currentTime <= endTime) {
//2、效验随机码和商品id
String randomCode = redisTo.getRandomCode();
String skuId = redisTo.getPromotionSessionId() + "-" +redisTo.getSkuId();
if (randomCode.equals(key) && killId.equals(skuId)) {
//3、验证购物数量是否合理和库存量是否充足
Integer seckillLimit = redisTo.getSeckillLimit();
//获取信号量
String seckillCount = redisTemplate.opsForValue().get(SKU_STOCK_SEMAPHORE + randomCode);
Integer count = Integer.valueOf(seckillCount);
//判断信号量是否大于0,并且买的数量不能超过库存
if (count > 0 && num <= seckillLimit && count > num ) {
//4、验证这个人是否已经买过了(幂等性处理),如果秒杀成功,就去占位。userId-sessionId-skuId
//SETNX 原子性处理
String redisKey = user.getId() + "-" + skuId;
//设置自动过期(活动结束时间-当前时间)
Long ttl = endTime - currentTime;
Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
if (aBoolean) {
//占位成功说明从来没有买过,分布式锁(获取信号量-1)
RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
//TODO 秒杀成功,快速下单
boolean semaphoreCount = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
//保证Redis中还有商品库存
if (semaphoreCount) {
//创建订单号和订单信息发送给MQ
// 秒杀成功 快速下单 发送消息到 MQ 整个操作时间在 10ms 左右
String timeId = IdWorker.getTimeId();
SeckillOrderTo orderTo = new SeckillOrderTo();
orderTo.setOrderSn(timeId);
orderTo.setMemberId(user.getId());
orderTo.setNum(num);
orderTo.setPromotionSessionId(redisTo.getPromotionSessionId());
orderTo.setSkuId(redisTo.getSkuId());
orderTo.setSeckillPrice(redisTo.getSeckillPrice());
rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",orderTo);
long s2 = System.currentTimeMillis();
log.info("耗时..." + (s2 - s1));
return timeId;
}
}
}
}
}
long s3 = System.currentTimeMillis();
log.info("耗时..." + (s3 - s1));
return null;
}
redisson信号量
边栏推荐
- 关于MySQL的30条优化技巧,超实用
- leecode-学习笔记
- Realize reverse proxy client IP transparent transmission
- Starting from 1.5, build a micro Service Framework -- log tracking traceid
- Unity Max and min constraint adjustment
- Global and Chinese markets for reciprocating seal compressors 2022-2028: Research Report on technology, participants, trends, market size and share
- fibonacci search
- Arduino 测量交流电流
- 3 find the greatest common divisor and the least common multiple
- CJ mccullem autograph: to dear Portland
猜你喜欢
Yiwen gets rid of the garbage collector
Lesson 1: serpentine matrix
一文搞定JVM常见工具和优化策略
Google Maps case
Realize reverse proxy client IP transparent transmission
Common JVM tools and optimization strategies
Thoroughly understand JVM class loading subsystem
Leetcode daily question 1189 The maximum number of "balloons" simple simulation questions~
【无标题】
TCC of distributed solutions
随机推荐
Hcip day 11 (BGP agreement)
SPSS analysis of employment problems of college graduates
Metasploit (MSF) uses MS17_ 010 (eternal blue) encoding:: undefined conversionerror problem
Selenium+pytest automated test framework practice
Tensor attribute statistics
Hj16 shopping list
LeetCode145. Post order traversal of binary tree (three methods of recursion and iteration)
Finally understand what dynamic planning is
视频标准二三事
The maximum happiness of the party
Week 17 homework
2022 registration examination for safety management personnel of hazardous chemical business units and simulated reexamination examination for safety management personnel of hazardous chemical busines
Detailed explanation of pointer and array written test of C language
Global and Chinese markets for children's amusement facilities 2022-2028: Research Report on technology, participants, trends, market size and share
Composition of interface
[untitled]
Paddy serving v0.9.0 heavy release multi machine multi card distributed reasoning framework
audiopolicy
Realize reverse proxy client IP transparent transmission
Simple and beautiful method of PPT color matching