当前位置:网站首页>秒杀系统的设计与实现思路
秒杀系统的设计与实现思路
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信号量
边栏推荐
- Douban scoring applet Part-2
- Tensor attribute statistics
- Simple and beautiful method of PPT color matching
- Hcip day 11 (BGP agreement)
- Un article traite de la microstructure et des instructions de la classe
- Element operation and element waiting in Web Automation
- audiopolicy
- 2.13 summary
- [secretly kill little buddy pytorch20 days] - [Day2] - [example of picture data modeling process]
- 30 optimization skills about mysql, super practical
猜你喜欢

一文搞定JVM的内存结构

链表之双指针(快慢指针,先后指针,首尾指针)

Week 17 homework

VOT toolkit environment configuration and use

基于STM32的ADC采样序列频谱分析
![[secretly kill little buddy pytorch20 days] - [Day2] - [example of picture data modeling process]](/img/41/4de83d2c81b9e3485d503758e12108.jpg)
[secretly kill little buddy pytorch20 days] - [Day2] - [example of picture data modeling process]

Selenium+pytest automated test framework practice

Three. JS VR house viewing

视频标准二三事

Detailed explanation of pointer and array written test of C language
随机推荐
Methods modified by static
Un article traite de la microstructure et des instructions de la classe
Use the rewrite rule to rewrite all accesses to the a domain name to the B domain name
Marginal probability and conditional probability
链表之双指针(快慢指针,先后指针,首尾指针)
February 13, 2022-4-symmetric binary tree
Metasploit(msf)利用ms17_010(永恒之蓝)出现Encoding::UndefinedConversionError问题
Selenium+pytest automated test framework practice
VIM tail head intercept file import
My experience and summary of the new Zhongtai model
C Primer Plus Chapter 9 question 9 POW function
Déterminer si un arbre binaire est un arbre binaire complet
Hcip day 11 (BGP agreement)
判断二叉树是否为完全二叉树
Using LNMP to build WordPress sites
Three. JS VR house viewing
Masked Autoencoders Are Scalable Vision Learners (MAE)
Leetcode buys and sells stocks
Commonly used probability distributions: Bernoulli distribution, binomial distribution, polynomial distribution, Gaussian distribution, exponential distribution, Laplace distribution and Dirac delta d
30 optimization skills about mysql, super practical