基于之前的秒杀功能的优化(包括Sentinel在SpringBoot中的简单应用)

这篇博客主要是对自己之前写的博客的一次优化,可以结合下面两篇博客进行这篇博客的阅读:

对自己关于秒杀功能的一次访谈与实战-CSDN博客

SpringBoot中使用Sharding-JDBC实战(实战+版本兼容+Bug解决)-CSDN博客

开始正题:

 一、新增亮点

1.引入Sentinel进行限流

1.1 依赖

这里我是用的SpringCloud中Sentinel的依赖,所以也需要引入SpringCloud的依赖:

在root的pom.xml中引入下面依赖

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2023.0.1</version><type>pom</type><scope>import</scope>
</dependency><!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-alibaba-dependencies -->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>2023.0.1.0</version><type>pom</type>
</dependency>

在server的pom.xml中引入Sentinel的依赖:

<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactI
</dependency><dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-annotation-aspectj</artifactId>
</dependency>

1.2 初始化限流配置

这里需要自定义资源的名称,用于后续注解中加入,还有定义限流规则,限流数量

package com.quick.config;import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.List;/*** 初始化限流配置*/
@Component
public class SentinelRuleConfig implements InitializingBean {@Overridepublic void afterPropertiesSet() throws Exception {List<FlowRule> rules = new ArrayList<>();// 创建抢购秒杀积分包的 Sentinel 规则FlowRule seckillIntegralPackageRule = new FlowRule();seckillIntegralPackageRule.setResource("seckill_integral-package");seckillIntegralPackageRule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 限流模式:按 QPSseckillIntegralPackageRule.setCount(40); // 每秒最多允许 40 人rules.add(seckillIntegralPackageRule);// 加载规则到 SentinelFlowRuleManager.loadRules(rules);}
}

1.3 自定义流控策略

package com.quick.handler;import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.quick.exception.SentinelException;/*** 自定义流控策略*/
public class CustomBlockHandler {public static void seckillIntegralPackageBlockHandlerMethod(BlockException exception) {throw new SentinelException("当前抢购人数过多,请稍后再试...");}
}

1.4 接口上注解使用

/*** 秒杀积分包实现,加入订单* @param integralPackageId 积分包id* @return 订单id*/@SentinelResource(value = "seckill_integral-package",blockHandler = "seckillIntegralPackageBlockHandlerMethod",blockHandlerClass = CustomBlockHandler.class)@PostMapping("seckillIntegralPackage/{id}")@Operation(summary = "用户抢购秒杀积分包")public Result<Long> seckillIntegralPackage(@PathVariable("id") Long integralPackageId) {return integralPackageOrderService.seckillIntegralPackage(integralPackageId);}

对秒杀进行限流,能够让秒杀系统更加稳定。

2.对积分订单分表

参考下面我这个博客,里面详细讲解分表操作和使用Jmeter进行压测:

SpringBoot中使用Sharding-JDBC实战(实战+版本兼容+Bug解决)-CSDN博客

二、完善删除功能

1.重构秒杀券表

在之前删除秒杀券是进行直接的表中数据删除,这次修改是进行假删除,增加一个 is_delete 字段,修改后的表结构如下:

2.重构管理端删除秒杀券功能

3.重构管理端查看积分包功能

4.重构管理端启用禁用积分包

5.重构管理端修改积分包功能

6.重构用户查看积分包功能

7.重构用户查看积分包库存功能

8.重构用户查看自己的积分包订单

这样子的优化是,即使管理员下架了积分包,用户还能看到自己曾经抢购过的积分包。

IntegralPackageOrderVO:
/*** <p>* 积分包订单* </p>** @author bluefoxyu* @since 2024-10-15*/
@Data
public class IntegralPackageOrderVO implements Serializable {@Schema(description = "积分包订单id")private Long orderId;@Schema(description = "积分包名")private String name;@Schema(description = "积分")private Long integral;@Schema(description = "积分包描述")private String description;@Schema(description = "积分包状态(0已下架,1抢购中)")private Integer IntegralPackageStatus;@Schema(description = "是否使用(0未使用 1已使用)")private Integer hasUse;@Schema(description = "抢购时间")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime createTime;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")@Schema(description = "开始时间")private LocalDateTime beginTime;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")@Schema(description = "结束时间")private LocalDateTime endTime;}
IntegralPackageStatus:
package com.quick.constant;import io.swagger.v3.oas.annotations.media.Schema;@Schema(description = "积分包状态(0已下架,1抢购中)")
public class IntegralPackageStatus {//抢购中public static final Integer PANIC_BUYING = 1;//已下架public static final Integer REMOVED = 0;}
IntegralPackageOrderServiceImpl:
@Overridepublic List<IntegralPackageOrderVO> getMyOrder() {// 获取当前用户IDLong userId = BaseContext.getCurrentId();// 缓存键String cacheKey = INTEGRALPACKAGE_MY_ORDER_KEY + userId;// 从缓存中获取数据String cachedData = stringRedisTemplate.opsForValue().get(cacheKey);if (cachedData != null) {// 如果缓存中有数据,直接反序列化返回return JSONUtil.toList(cachedData, IntegralPackageOrderVO.class);}// 从数据库查询用户订单LambdaQueryWrapper<IntegralPackageOrder> orderLambdaQueryWrapper = Wrappers.lambdaQuery(IntegralPackageOrder.class).eq(IntegralPackageOrder::getUserId, userId);List<IntegralPackageOrder> integralPackageOrders = integralPackageOrderMapper.selectList(orderLambdaQueryWrapper);// 构建返回结果List<IntegralPackageOrderVO> integralPackageOrderVOS = new ArrayList<>();for (IntegralPackageOrder integralPackageOrder : integralPackageOrders) {Long integralPackageId = integralPackageOrder.getIntegralPackageId();IntegralPackage integralPackage = integralPackageMapper.selectById(integralPackageId);// 拷贝积分包信息IntegralPackageOrderVO integralPackageOrderVO = BeanUtil.copyProperties(integralPackage, IntegralPackageOrderVO.class);// 判断积分包状态Integer integralPackageStatus = IntegralPackageStatus.PANIC_BUYING;if (integralPackage.getIsDelete() == 0) {integralPackageStatus = IntegralPackageStatus.REMOVED;}// 完善积分订单信息integralPackageOrderVO.setOrderId(integralPackageOrder.getId());integralPackageOrderVO.setHasUse(integralPackageOrder.getHasUse());integralPackageOrderVO.setCreateTime(integralPackageOrder.getCreateTime());integralPackageOrderVO.setIntegralPackageStatus(integralPackageStatus);integralPackageOrderVOS.add(integralPackageOrderVO);}// 序列化结果并存入缓存,设置过期时间为一天String jsonData = JSONUtil.toJsonStr(integralPackageOrderVOS);stringRedisTemplate.opsForValue().set(cacheKey, jsonData, 1,TimeUnit.DAYS);return integralPackageOrderVOS;}

9.重构积分订单的积分计入用户

这里的主要优化是防止缓存穿透和对计入实现加分布式锁

@Overridepublic String addIntegralInUser(Long orderId,Long integral) {Long userId = BaseContext.getCurrentId();String cacheKey = INTEGRALPACKAGE_INPUT_KEY + orderId;// 从缓存中读取String json = stringRedisTemplate.opsForValue().get(cacheKey);if (json == null) {// 查询数据库LambdaQueryWrapper<IntegralPackageOrder> orderLambdaQueryWrapper = Wrappers.lambdaQuery(IntegralPackageOrder.class).eq(IntegralPackageOrder::getUserId, userId).eq(IntegralPackageOrder::getId, orderId);IntegralPackageOrder integralPackageOrder = integralPackageOrderMapper.selectOne(orderLambdaQueryWrapper);if (integralPackageOrder == null) {// 缓存空值防止缓存穿透stringRedisTemplate.opsForValue().set(cacheKey, JSONUtil.toJsonStr(integralPackageOrder), 3, TimeUnit.MINUTES);throw new IntegralPackageOrderException("暂无可用秒杀积分包订单可计入用户积分");}// 写入缓存stringRedisTemplate.opsForValue().set(cacheKey, JSONUtil.toJsonStr(integralPackageOrder), 3, TimeUnit.MINUTES);}// 不为空,解析IntegralPackageOrder integralPackageOrder = JSONUtil.toBean(json, IntegralPackageOrder.class);// 校验字段是否有效if (integralPackageOrder.getHasUse() == null || Objects.equals(integralPackageOrder.getHasUse(), 1)) {throw new IntegralPackageOrderException("该积分包订单已使用,无法重复计入");}// 创建锁对象RLock redisLock = redissonClient.getLock(RedissonConstant.LOCK_INTEGRALPACKAGEORDER_MYORDER_KEY + userId);boolean isLock = redisLock.tryLock();if (!isLock) {throw new IntegralPackageException(MessageConstant.DUPLICATE_ORDERS_ADD_USER_ARE_NOT_ALLOWED);}try {User user = userMapper.selectById(userId);user.setWallet(user.getWallet() + integral);userMapper.updateById(user);UserUpdate userUpdate = UserUpdate.builder().userId(user.getId()).build();userUpdateService.saveOrUpdate(userUpdate);// 更新订单状态为已使用integralPackageOrder.setHasUse(1);int update = integralPackageOrderMapper.updateById(integralPackageOrder);if (update == 1) {return "积分计入成功!";}} finally {redisLock.unlock();}return "系统繁忙,积分计入失败";}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/477592.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis Search系列 - 第七讲 Windows(CygWin)编译Friso

目录 一、背景二、安装CygWin三、编译Friso四、运行Friso五、Friso分词效果测试 一、背景 最近在做RedisSearch的中文分词效果调研&#xff0c;底层的中文分词插件使用的就是Friso&#xff0c;目前手里的Linux环境上yum镜像仓库有问题导致没法安装gcc&#xff0c;又急于验证Fr…

(动画)Qt控件 QLCDNumer

文章目录 LCD Number1. 介绍2. 核心属性3 . 代码实现:倒计时1. 在界⾯上创建⼀个 QLCDNumber , 初始值设为 10.2. 修改 widget.h 代码, 创建⼀个 QTimer 成员, 和⼀个 updateTime 函数3. 修改 widget.cpp, 在构造函数中初始化 QTimer4. 修改 widget.cpp, 实现 updateTime 4. 动…

《操作系统 - 清华大学》4 -5:非连续内存分配:页表一反向页表

文章目录 1. 大地址空间的问题2. 页寄存器&#xff08; Page Registers &#xff09;方案3. 基于关联内存(associative memory )的反向页表&#xff08;inverted page table&#xff09;4. 基于哈希&#xff08;hashed&#xff09;查找的反向页表5. 小结 1. 大地址空间的问题 …

web前端开发--动画效果

1、3D旋转 <!DOCTYPE html> <html><head><meta charset"UTF-8"><title>3D旋转</title><style type"text/css">div{/*设置大盒子样式*/width: 100px;height: 100px;/*background-color: rgba(255,0,0,0.5);*/ba…

linux入门——“僵尸进程、孤儿进程”

引入 在linux中&#xff0c;特别是我们自己写代码时&#xff0c;使用fork&#xff08;&#xff09;创建子进程的时候&#xff0c;需要知道两种特殊的进程——僵尸进程、孤儿进程。这是我们不可忽视的进程的两种特殊情况。 一、僵尸进程 在C语言编程中&#xff0c;僵尸进程的出…

【数据结构 | C++】部落

在一个社区里&#xff0c;每个人都有自己的小圈子&#xff0c;还可能同时属于很多不同的朋友圈。我们认为朋友的朋友都算在一个部落里&#xff0c;于是要请你统计一下&#xff0c;在一个给定社区中&#xff0c;到底有多少个互不相交的部落&#xff1f;并且检查任意两个人是否属…

维护在线重做日志(一)

学习目标 解释在线重做日志文件的目的概述在线重做日志文件的结构控制日志开关和检查点多路复用和维护在线重做日志文件使用OMF管理在线重做日志文件获取在线重做日志文件信息 在线重做日志文件提供了在数据库发生故障时重做事务的方法。 每个事务都同步写入重做日志缓冲区&a…

mayo介绍和QTqmake编译基于Opencascade开发的mayo工程-小白配置

目录: 一、mayo介绍:zap: 最新功能&#xff08;截止7.8.2&#xff09;在这里插入图片描述 二、编译准备三、编译过程3.1QT Creator打开源码的pro工程3.2修改几处pro配置3.3复制所需的动态链接库3.4编译完成 一、mayo介绍 1️⃣mayo是一个基于opencascade开源库开发的一个开源CA…

【Github】如何使用Git将本地项目上传到Github

【Github】如何使用Git将本地项目上传到Github 写在最前面1. 注册Github账号2. 安装Git工具配置用户名和邮箱仅为当前项目配置&#xff08;可选&#xff09; 3. 创建Github仓库4. 获取仓库地址5. 本地操作&#xff08;1&#xff09;进入项目文件夹&#xff08;2&#xff09;克隆…

【layui】table的switch、edit修改

<title>简单表格数据</title><div class"layui-card layadmin-header"><div class"layui-breadcrumb" lay-filter"breadcrumb"><a>系统设置</a><a>简单表格数据</a></div> </div>&…

Spring Template

Thymeleaf 入门 Web 开发离不开动态页面的开发&#xff0c;很早以前企业主要使用 JSP 技术来开发网页&#xff0c;随着技术的升级更替&#xff0c;目前来说最主流的方案是&#xff1a;Thymeleaf&#xff0c;Thymeleaf 是一个模板框架&#xff0c;它可以支持多种格式的内容动态…

【大语言模型】ACL2024论文-20 SCIMON:面向新颖性的科学启示机器优化

【大语言模型】ACL2024论文-20 SCIMON&#xff1a;面向新颖性的科学启示机器优化 目录 文章目录 【大语言模型】ACL2024论文-20 SCIMON&#xff1a;面向新颖性的科学启示机器优化目录摘要研究背景问题与挑战如何解决创新点算法模型实验效果推荐阅读指数&#xff1a;★★★★☆ …

HTML实现 扫雷游戏

前言&#xff1a; 游戏起源与发展 扫雷游戏的雏形可追溯到 1973 年的 “方块&#xff08;cube&#xff09;” 游戏&#xff0c;后经改编出现了 “rlogic” 游戏&#xff0c;玩家需为指挥中心探出安全路线避开地雷。在此基础上&#xff0c;开发者汤姆・安德森编写出了扫雷游戏的…

docker镜像源配置、换源、dockerhub国内镜像最新可用加速源(仓库)

一、临时拉取方式 在docker pull后先拼接镜像源域名&#xff0c;后面拼接拉取的镜像名 $ docker pull dockerpull.org/continuumio/miniconda3 二、永久配置方式 vim修改/etc/docker/daemon.json&#xff0c;并重启docker服务。 # 创建目录 sudo mkdir -p /etc/docker# 写…

AMD(Xilinx) FPGA配置Flash大小选择

目录 1 FPGA配置Flash大小的决定因素2 为什么选择的Flash容量大小为最小保证能够完成整个FPGA的配置呢&#xff1f; 1 FPGA配置Flash大小的决定因素 在进行FPGA硬件设计时&#xff0c;选择合适的配置Flash是我们进行硬件设计必须考虑的&#xff0c;那么配置Flash大小的选择由什…

NVR录像机汇聚管理EasyNVR多品牌NVR管理工具/设备如何使用Docker运行?

在当今的安防监控领域&#xff0c;随着视频监控技术的不断发展和应用范围的扩大&#xff0c;如何高效、稳定地管理并分发视频流资源成为了行业内外关注的焦点。EasyNVR作为一款功能强大的多品牌NVR管理工具/设备&#xff0c;凭借其灵活的部署方式和卓越的性能&#xff0c;正在引…

【SKFramework框架】一、框架介绍

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享QQ群&#xff1a;398291828小红书小破站 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 【Unity3D框架】SKFramework框架完全教程《全…

Python 版本的 2024详细代码

2048游戏的Python实现 概述&#xff1a; 2048是一款流行的单人益智游戏&#xff0c;玩家通过滑动数字瓷砖来合并相同的数字&#xff0c;目标是合成2048这个数字。本文将介绍如何使用Python和Pygame库实现2048游戏的基本功能&#xff0c;包括游戏逻辑、界面绘制和用户交互。 主…

排序算法(选择排序、直接插入排序、冒泡排序、二路归并排序)(C语言版)

对数组进行排序&#xff0c;主要演示选择排序、直接排序、冒泡排序、二路归并排序算法&#xff0c;附上代码演示 一、编写好各类排序方法的函数 &#xff08;1&#xff09; s_sort(int e[],int n):选择排序。 &#xff08;2&#xff09;si_sort(int e[],int n):直接插人排序。…

Unity图形学之Surface Shader结构

1.没有Pass&#xff1a;因为Render Path已经封装好了 Shader "Custom/Test" {Properties{_Color ("Color", Color) (1,1,1,1)_MainTex ("Albedo (RGB)", 2D) "white" {}_Glossiness ("Smoothness", Range(0,1)) 0.5_Meta…