当前位置: 首页 > news >正文

谷粒商城の秒杀服务

文章目录

  • 前言
  • 一、秒杀系统的设计
  • 二、缓存预热
    • 1.缓存结构设计
    • 2、上架
  • 三、秒杀业务实现


前言

  本篇基于谷粒商城的秒杀服务,介绍设计一个秒杀系统的要素,包括缓存预热商品随机码动静分离消息队列削峰等。对应视频P311-P325(只介绍系统设计和后端代码的关键部分)


一、秒杀系统的设计

  对于短时间内高并发的秒杀场景,在系统的架构方面,首先应该做到服务自治。即拆分一个专门的微服务去应对秒杀相关的业务请求,具体创建订单,扣减库存,支付可以远程调用其他服务。这样做的目的是为了即使秒杀服务扛不住压力崩溃了,也不会对其他的服务造成影响,也是单一职责的体现。
  其次在安全方面,需要对秒杀的链接进行加密,或为每一个秒杀的商品生成随机码,用户请求时不仅需要带着商品id,还需要加上随机码,防止恶意攻击,以及在网关层识别非法攻击请求并且拦截。
  在流量控制方面,可以使用验证码等手段进行流量分担(用户输入验证码的速度有快有慢),以及引入消息队列,只将请求的关键信息放入消息队列,然后返回给用户提示信息,让队列自己去消费。最后还应该做好熔断降级
  除了上述几点,为了提高系统的响应速度,还需要进行缓存预热,将秒杀的商品信息,场次信息,库存信息提前存入Redis中,避免大量的请求全部访问数据库,以及Nginx做好动静分离
  附一个使用AES实现链接加密的简单案例:
  AES加密工具类:

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;public class AesEncryptionUtil {private static final String ALGORITHM = "AES";public static String encrypt(String data, String secret) throws Exception {SecretKeySpec key = new SecretKeySpec(secret.getBytes(), ALGORITHM);Cipher cipher = Cipher.getInstance(ALGORITHM);cipher.init(Cipher.ENCRYPT_MODE, key);byte[] encrypted = cipher.doFinal(data.getBytes());return Base64.getEncoder().encodeToString(encrypted);}public static String decrypt(String encryptedData, String secret) throws Exception {SecretKeySpec key = new SecretKeySpec(secret.getBytes(), ALGORITHM);Cipher cipher = Cipher.getInstance(ALGORITHM);cipher.init(Cipher.DECRYPT_MODE, key);byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedData));return new String(decrypted);}
}

  Controller:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
public class ResourceController {private static final String SECRET_KEY = "1234567890123456"; // 16位密钥@GetMapping("/encrypt")public String encrypt(@RequestParam String id) {try {String encryptedId = AesEncryptionUtil.encrypt(id, SECRET_KEY);return "Encrypted ID: " + encryptedId;} catch (Exception e) {e.printStackTrace();return "Error encrypting ID";}}@GetMapping("/resource")public String getResource(@RequestParam String id) {try {String decryptedId = AesEncryptionUtil.decrypt(id, SECRET_KEY);return "Resource ID: " + decryptedId;} catch (Exception e) {e.printStackTrace();return "Error decrypting ID";}}
}

  在访问资源接口/resource前,首先访问/encrypt连接获取加密ID:

http://localhost:8080/encrypt?id=123

  前端保存加密后的ID,带着这个ID去访问资源接口:

http://localhost:8080/resource?id=<encryptedId>

二、缓存预热

1.缓存结构设计

  在本项目中,选择将秒杀场次、库存量、商品信息进行缓存预热:

  • 秒杀场次设计为List结构,key是开始事件的毫秒值_结束时间的毫秒值,value是场次_skuId。
  • 库存量设计为String结构,key是固定前缀:随机码,value则是具体的库存。
  • 商品信息设计为hash结构,key是场次_skuId,value则是具体商品信息的对象。

2、上架

  本项目中使用缓存预热的方式是定时任务,提前将今明后三天的秒杀信息放入缓存,并且设计上使用了双检锁模式。在定时任务执行处使用分布式缓存锁,防止多实例同时运行,并且在执行相关业务代码的时候再次进行了判断,如果缓存中已经有了对应的key,则不再重复向Redis中保存。限流也使用了Redisson中的semaphore防止并发问题。

    // 每天凌晨 3 点执行@Scheduled(cron = "0 0 3 * * *")public void executeTaskAt3AMUpSeckillSku() {//加分布式锁,防止多实例重复执行RLock lock = redissonClient.getLock(UPLOAD_LOCK);try {lock.lock();secKillSkuService.uploadSecKillSkuInfo();} finally {lock.unlock();}}
    @Overridepublic void uploadSecKillSkuInfo() {List<SeckillSessionPojo> lasted3SeckillInfo = couponRemoteServiceClient.getLasted3SeckillInfo();if (!CollectionUtils.isEmpty(lasted3SeckillInfo)) {//将活动信息进行缓存 key:前缀:开始事件_结束时间 value SkuIdthis.saveSessionInfos(lasted3SeckillInfo);//保存商品信息 key:前缀 value 商品信息this.saveSessionSkuInfos(lasted3SeckillInfo);}}private void saveSessionInfos(List<SeckillSessionPojo> lasted3SeckillInfo) {lasted3SeckillInfo.forEach(seckillSessionPojo -> {long start = seckillSessionPojo.getStartTime().getTime();long end = seckillSessionPojo.getEndTime().getTime();String key = SESSION_CACHE_PREFIX + start + "_" + end;Boolean hasKey = stringRedisTemplate.hasKey(key);if (!hasKey) {//活动场次id_商品idList<String> ids = seckillSessionPojo.getSkuRelationEntities().stream().map(seckillSkuRelationPojo ->seckillSkuRelationPojo.getPromotionSessionId().toString()+"_"+seckillSkuRelationPojo.getSkuId().toString()).collect(Collectors.toList());stringRedisTemplate.opsForList().leftPushAll(key, ids);}});}private void saveSessionSkuInfos(List<SeckillSessionPojo> lasted3SeckillInfo) {lasted3SeckillInfo.forEach(seckillSessionPojo -> {//准备hash操作,一个活动场次一个hash操作BoundHashOperations<String, Object, Object> operations = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);List<SeckillSkuRelationEntity> relations = seckillSessionPojo.getSkuRelationEntities();relations.forEach(relation -> {String token = UUID.randomUUID().toString().replace("-", "");if (Boolean.FALSE.equals(operations.hasKey(relation.getPromotionSessionId().toString()+"_"+relation.getSkuId().toString()))) {//缓存商品,一个活动场次对应的具体商品SecKillSkuRedisTO secKillSkuRedisTO = new SecKillSkuRedisTO();BeanUtils.copyProperties(relation, secKillSkuRedisTO);//还应该设置商品详细信息 远程调用product服务SkuInfoPojo skuInfo = null;try {skuInfo = productRemoteServiceClient.getSkuInfo(relation.getSkuId());} catch (Exception e) {log.info("根据skuId:{}查询商品服务错误:", relation.getSkuId(), e);}secKillSkuRedisTO.setSkuInfo(skuInfo);//设置商品的开始事件和结束时间secKillSkuRedisTO.setStartTime(seckillSessionPojo.getStartTime().getTime());secKillSkuRedisTO.setEndTime(seckillSessionPojo.getEndTime().getTime());//设置随机码secKillSkuRedisTO.setRandomCode(token);String result = JSON.toJSONString(secKillSkuRedisTO);operations.put(relation.getPromotionSessionId().toString()+"_"+relation.getSkuId().toString(), result);//限流 相比较于固定的skuId,每次的随机码都不一样RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);//商品可以秒杀的总量作为信号量semaphore.trySetPermits(relation.getSeckillCount().intValue());}});});}

三、秒杀业务实现

  在秒杀业务的具体实现上:

  1. 在拦截器中判断用户是否登录。
  2. 进行场次判断,是否在秒杀时间段中。
  3. 参数中的商品随机码和场次_skuId是否与缓存中预热的一致。
  4. 校验用户是否已经参加过该场次该商品的秒杀(使用Redis的setNX命令)。
  5. 从信号量中扣去库存(尝试扣去库存使用有超时时间的获取,超过时间获取不到就自己放弃,不会死等)。
  6. 向Rabbit MQ发送消息,订单服务监听,消费消息进行订单创建。
    @Overridepublic String kill(String killId, String key, Integer num) {MemberRespVO memberRespVO = LoginInterceptor.threadLocal.get();Long userId = memberRespVO.getId();String timeId = IdWorker.getTimeId();//首先校验用户是否登录(在拦截器中已经实现)//校验信息是否合法BoundHashOperations<String, Object, Object> operations = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);//获取killId的场次信息String json = (String) operations.get(killId);if (!StringUtils.isBlank(json)){SecKillSkuRedisTO secKillSkuRedisTO = JSON.parseObject(json, SecKillSkuRedisTO.class);Long startTime = secKillSkuRedisTO.getStartTime();Long endTime = secKillSkuRedisTO.getEndTime();long time = new Date().getTime();//校验时间if (startTime > time || endTime < time) {return null;}String randomCode = secKillSkuRedisTO.getRandomCode();Long promotionSessionId = secKillSkuRedisTO.getPromotionSessionId();Long skuId = secKillSkuRedisTO.getSkuId();//校验参数中的随机码和场次_skuId与redis中的是否一致if (!key.equals(randomCode) || !killId.equals(promotionSessionId+"_"+skuId)) {return null;}//校验该用户是否已经秒杀过String userKey = new StringBuffer().append(userId).append("_").append(promotionSessionId).append("_").append(skuId).toString();//setIfAbsent 只有不存在才会创建Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(userKey, num.toString(), 100, TimeUnit.MILLISECONDS);if (!aBoolean) {return null;}//扣减库存RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);try {//利用有超时时间的获取,超过时间获取不到就自己放弃,不会死等boolean b = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);if (!b){return null;}//向rabbitMQ发消息,创建订单SecKillRabbitTO secKillRabbitTO = new SecKillRabbitTO();secKillRabbitTO.setMemberId(userId);secKillRabbitTO.setNum(num);secKillRabbitTO.setPromotionSessionId(promotionSessionId);secKillRabbitTO.setSkuId(skuId);secKillRabbitTO.setOrderNo(timeId);rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",secKillRabbitTO);} catch (InterruptedException e) {return null;}}return timeId;}

相关文章:

谷粒商城の秒杀服务

文章目录 前言一、秒杀系统的设计二、缓存预热1.缓存结构设计2、上架 三、秒杀业务实现 前言 本篇基于谷粒商城的秒杀服务&#xff0c;介绍设计一个秒杀系统的要素&#xff0c;包括缓存预热、商品随机码、动静分离、消息队列削峰等。对应视频P311-P325&#xff08;只介绍系统设…...

庆祝程序员节:聊一聊编程语言的演变

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…...

大模型技术在网络安全领域的应用与发展

一、概述 大模型技术&#xff0c;尤其是深度学习和自然语言处理领域的大型预训练模型&#xff0c;近年来在网络安全领域得到了广泛应用。这些模型通过其强大的数据处理能力和泛化能力&#xff0c;为网络安全带来了新的机遇和挑战。本文将对大模型技术在网络安全领域的应用进行…...

基于vite和vue3、 eslint、prettier、stylelint、husky规范

前言 在现代的前端开发中&#xff0c;代码规范非常重要。它可以提高团队的协作效率&#xff0c;减少代码错误&#xff0c;使代码更易于维护。为了实现代码规范化&#xff0c;我们可以使用一些工具来辅助我们的开发流程&#xff0c;包括eslint、prettier、stylelint、husky&am…...

git push到远程怎么回退

git push到远程服务器想继续修改&#xff0c;你必须要回退然后在此提交。而且需要保留本地的修改文件。 下面给你一些git命令&#xff0c;回退很简单。 按照下面的流程操作就行&#xff1a; 1.查看提交历史 首先&#xff0c;使用git log命令查看提交历史。可以使用以下命令显…...

Web保存状态的手段(Application的使用)

Application 在Java Web开发中&#xff0c;ServletContext&#xff08;通常称为application&#xff09;是一个非常重要的接口&#xff0c;它代表了Web应用程序的上下文。每个Web应用都有其自己的ServletContext&#xff0c;当Web应用被加载到Servlet容器时创建&#xff0c;并…...

高翔【自动驾驶与机器人中的SLAM技术】学习笔记(十二)拓展图优化库g2o(一)框架

【转载】理解图优化&#xff0c;一步步带你看懂g2o框架 文章来源&#xff1a;理解图优化&#xff0c;一步步带你看懂g2o框架 小白&#xff1a;师兄师兄&#xff0c;最近我在看SLAM的优化算法&#xff0c;有种方法叫“图优化”&#xff0c;以前学习算法的时候还有一个优化方法…...

Flutter Row组件实战案例

In this section, we’ll continue our exploration by combining the Row and Container widgets to create more complex layouts. Let’s dive in! 在本节中&#xff0c;我们将继续探索&#xff0c;结合“Row”和“Container”小部件来创建更复杂的布局。让我们开始吧! Sc…...

【ubuntu20.04】【ROS Noetic】【ROS安装】【Website may be down.】【gpg: 找不到有效的 OpenPGP 数据。】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、登入www.ros.org1.Setup your sources.list2.Set up your keys中间出了点问题 gpg: 找不到有效的 OpenPGP 数据。4.Installation下载安装ros5.环境参数的配…...

Python开发必备,这些黑科技库你get到了吗

大家好,今天我要为大家推荐一些非常强大和实用的Python库,相信无论是Python新手还是老司机,都能从中受益,提升你的Python开发技能。那就一起来看看吧! 1.Requests: 简单好用的HTTP请求库 第一个要介绍的是Requests库。它是Python中最流行的HTTP客户端库之一,大大简化了网络请…...

sublime text 常用快捷键

sublimetext常用快捷键 CtrlShiftP&#xff1a;打开命令面板 CtrlP&#xff1a;搜索项目中的文件 CtrlG&#xff1a;跳转到第几行 CtrlW&#xff1a;关闭当前打开文件 CtrlShiftW&#xff1a;关闭所有打开文件 CtrlShiftV&#xff1a;粘贴并格式化 CtrlD&#xff1a;选择单词&a…...

Kubernetes(K8S) + Harbor + Ingress 部署 SpringBoot + Vue 前后端分离项目

文章目录 1、环境准备2、搭建 K8S3、搭建 Harbor4、搭建 MySQL5、构建 SpringBoot 项目镜像6、构建 Vue.js 项目镜像7、部署项目7.1、配置 NameSpace7.2、配置 Deployment、Service7.3、配置 Ingress-Nginx7.4、访问测试 1、环境准备 本次整体项目部署使用的是阿里云ECS服务器…...

【iOS】知乎日报第一周总结

知乎日报第一周总结 文章目录 知乎日报第一周总结前言网络异步导致视图无法加载加载网络上的图片实现一个上拉刷新的效果左上角的时间初步实现了点击cell进入网页小结 前言 笔者在本周算是正式开始写项目了&#xff0c;本周主要是大致完成了主页的内容&#xff0c;大致完成了主…...

Springboot整合spring-boot-starter-data-elasticsearch

前言 <font style"color:rgb(36, 41, 47);">spring-boot-starter-data-elasticsearch</font> 是 Spring Boot 提供的一个起始依赖&#xff0c;旨在简化与 Elasticsearch 交互的开发过程。它集成了 Spring Data Elasticsearch&#xff0c;提供了一套完整…...

【大模型系列】mPLUG-Owl3(2024.08)

Paper: https://arxiv.org/pdf/2408.04840Github: https://github.com/X-PLUG/mPLUG-OwlHuggingFace&#xff1a;https://huggingface.co/mPLUG/mPLUG-Owl3-7B-240728Author: Jiabo Ye et al. 阿里巴巴 文章目录 0 总结(省流版)1 模型结构1.1 Cross-attention Based Achitectur…...

从0到1学习node.js(express模块)

文章目录 Express框架1、初体验express2、什么是路由3、路由的使用3、获取请求参数4、电商项目商品详情场景配置路由占位符规则5、小练习&#xff0c;根据id参数返回对应歌手信息6、express和原生http模块设置响应体的一些方法7、其他响应设置8、express中间件8.1、什么是中间件…...

MambaVision

核心速览 研究背景 研究问题 &#xff1a;这篇文章提出了一种新的混合Mamba-Transformer骨干网络&#xff0c;称为MambaVision&#xff0c;专为视 觉应用量身定制。研究的核心问题是如何有效地结合Mamba的状态空间模型&#xff08;SSM&#xff09;和Transf ormer的自注意力机制…...

MySQLDBA修炼之道-开发篇(二)

四、开发进阶 1. 范式和反范式 范式是数据库规范化的一个手段&#xff0c;是数据库设计中的一系列原理和技术&#xff0c;用于减少数据库中的数据冗余&#xff0c;并增进数据的一致性。 范式 1.1 第一范式 第一范式是指数据库表的每一列&#xff08;属性&#xff09;都是不可…...

前端必备的环境搭建

一、nvm安装详细教程&#xff08;安装nvm、node、npm、cnpm、yarn及环境变量配置&#xff09; 参考地址&#xff1a;nvm安装详细教程&#xff08;安装nvm、node、npm、cnpm、yarn及环境变量配置&#xff09;-CSDN博客 说明&#xff1a; 1&#xff09;关于nodejs目录不显示&a…...

SpringCloud笔记

什么是降级熔断&#xff1f;为什么要进行熔断&#xff1f; 熔断降级是一种分布式系统的保护机制&#xff0c;用于应对服务不稳定或不可用的情况。 熔断是指当某个服务的调用失败次数或异常比例达到一定阈值时&#xff0c;自动切断对该服务的调用&#xff0c;让请求快速失败&…...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

免费PDF转图片工具

免费PDF转图片工具 一款简单易用的PDF转图片工具&#xff0c;可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件&#xff0c;也不需要在线上传文件&#xff0c;保护您的隐私。 工具截图 主要特点 &#x1f680; 快速转换&#xff1a;本地转换&#xff0c;无需等待上…...

C++.OpenGL (20/64)混合(Blending)

混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...

k8s从入门到放弃之HPA控制器

k8s从入门到放弃之HPA控制器 Kubernetes中的Horizontal Pod Autoscaler (HPA)控制器是一种用于自动扩展部署、副本集或复制控制器中Pod数量的机制。它可以根据观察到的CPU利用率&#xff08;或其他自定义指标&#xff09;来调整这些对象的规模&#xff0c;从而帮助应用程序在负…...

【实施指南】Android客户端HTTPS双向认证实施指南

&#x1f510; 一、所需准备材料 证书文件&#xff08;6类核心文件&#xff09; 类型 格式 作用 Android端要求 CA根证书 .crt/.pem 验证服务器/客户端证书合法性 需预置到Android信任库 服务器证书 .crt 服务器身份证明 客户端需持有以验证服务器 客户端证书 .crt 客户端身份…...

边缘计算网关提升水产养殖尾水处理的远程运维效率

一、项目背景 随着水产养殖行业的快速发展&#xff0c;养殖尾水的处理成为了一个亟待解决的环保问题。传统的尾水处理方式不仅效率低下&#xff0c;而且难以实现精准监控和管理。为了提升尾水处理的效果和效率&#xff0c;同时降低人力成本&#xff0c;某大型水产养殖企业决定…...

IP选择注意事项

IP选择注意事项 MTP、FTP、EFUSE、EMEMORY选择时&#xff0c;需要考虑以下参数&#xff0c;然后确定后选择IP。 容量工作电压范围温度范围擦除、烧写速度/耗时读取所有bit的时间待机功耗擦写、烧写功耗面积所需要的mask layer...

多模态大语言模型arxiv论文略读(110)

CoVLA: Comprehensive Vision-Language-Action Dataset for Autonomous Driving ➡️ 论文标题&#xff1a;CoVLA: Comprehensive Vision-Language-Action Dataset for Autonomous Driving ➡️ 论文作者&#xff1a;Hidehisa Arai, Keita Miwa, Kento Sasaki, Yu Yamaguchi, …...

n8n:解锁自动化工作流的无限可能

在当今快节奏的数字时代&#xff0c;无论是企业还是个人&#xff0c;都渴望提高工作效率&#xff0c;减少重复性任务的繁琐操作。而 n8n&#xff0c;这个强大的开源自动化工具&#xff0c;就像一位智能的数字助手&#xff0c;悄然走进了许多人的工作和生活&#xff0c;成为提升…...

【HTML】HTML 与 CSS 基础教程

作为 Java 工程师&#xff0c;掌握 HTML 和 CSS 也是需要的&#xff0c;它能让你高效与前端团队协作、调试页面元素&#xff0c;甚至独立完成简单页面开发。本文将用最简洁的方式带你掌握核心概念。 一、HTML&#xff0c;网页骨架搭建 核心概念&#xff1a;HTML通过标签定义内…...