当前位置:网站首页>秒杀系统的设计与实现思路
秒杀系统的设计与实现思路
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信号量
边栏推荐
- 一文搞定JVM常见工具和优化策略
- openresty ngx_lua正則錶達式
- Paddy serving v0.9.0 heavy release multi machine multi card distributed reasoning framework
- VIM tail head intercept file import
- Leetcode buys and sells stocks
- First, redis summarizes the installation types
- Matlab smooth curve connection scatter diagram
- [untitled]
- Douban scoring applet Part-2
- [secretly kill little buddy pytorch20 days] - [Day2] - [example of picture data modeling process]
猜你喜欢
![[speech processing] speech signal denoising and denoising based on Matlab GUI low-pass filter [including Matlab source code 1708]](/img/df/9aa83ac5bd9f614942310a040a6dff.jpg)
[speech processing] speech signal denoising and denoising based on Matlab GUI low-pass filter [including Matlab source code 1708]

LeetCode102. Sequence traversal of binary tree (output by layer and unified output)

Tensor attribute statistics

audiopolicy

Detailed explanation of pointer and array written test of C language

鏈錶之雙指針(快慢指針,先後指針,首尾指針)

Paddle Serving v0.9.0 重磅发布多机多卡分布式推理框架
![[speech processing] speech signal denoising and denoising based on MATLAB low-pass filter [including Matlab source code 1709]](/img/f4/4d09dc05f5789b980ebd23cc352f8b.jpg)
[speech processing] speech signal denoising and denoising based on MATLAB low-pass filter [including Matlab source code 1709]

February 13, 2022-4-symmetric binary tree

Masked Autoencoders Are Scalable Vision Learners (MAE)
随机推荐
[speech processing] speech signal denoising and denoising based on MATLAB low-pass filter [including Matlab source code 1709]
Business introduction of Zhengda international futures company
Global and Chinese market of diesel fire pump 2022-2028: Research Report on technology, participants, trends, market size and share
链表之双指针(快慢指针,先后指针,首尾指针)
利用LNMP实现wordpress站点搭建
Use the rewrite rule to rewrite all accesses to the a domain name to the B domain name
终于搞懂什么是动态规划的
Activate function and its gradient
视频标准二三事
Starting from 1.5, build a micro Service Framework -- log tracking traceid
Global and Chinese markets for children's amusement facilities 2022-2028: Research Report on technology, participants, trends, market size and share
使用rewrite规则实现将所有到a域名的访问rewrite到b域名
Metasploit (MSF) uses MS17_ 010 (eternal blue) encoding:: undefined conversionerror problem
Negative sampling
Leetcode daily question 1189 The maximum number of "balloons" simple simulation questions~
openresty ngx_lua请求响应
openresty ngx_ Lua regular expression
Vcomp110.dll download -vcomp110 What if DLL is lost
C Primer Plus Chapter 9 question 9 POW function
Arduino measures AC current