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

基于之前的秒杀功能的优化(包括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 {

    @Override
    public 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); // 限流模式:按 QPS
        seckillIntegralPackageRule.setCount(40); // 每秒最多允许 40 人
        rules.add(seckillIntegralPackageRule);

        // 加载规则到 Sentinel
        FlowRuleManager.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:
@Override
    public List<IntegralPackageOrderVO> getMyOrder() {
        // 获取当前用户ID
        Long 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.重构积分订单的积分计入用户

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

@Override
    public 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.kler.cn/a/409213.html

相关文章:

  • 汽车HiL测试:利用TS-GNSS模拟器掌握硬件性能的仿真艺术
  • C#构建一个简单的循环神经网络,模拟对话
  • ISAAC Gym 7. 使用箭头进行数据可视化
  • ES6 、ESNext 规范、编译工具babel
  • 机器学习之量子机器学习(Quantum Machine Learning, QML)
  • C语言蓝桥杯组题目
  • 学习记录:js算法(一百零二):使用最小花费爬楼梯
  • 9.10Ubuntu网络编程环境配置,已解决
  • 力扣 41. 缺失的第一个正数
  • 【tomcat】tomcat的默认配置
  • 【Linux】详解shell代码实现(上)
  • postman 调用 下载接口(download)使用默认名称(response.txt 或随机名称)
  • A045-基于spring boot的个人博客系统的设计与实现
  • 数据结构 ——— 希尔排序算法的实现
  • 鸿蒙NEXT开发案例:二维码的生成与识别
  • Redis核心数据结构与高性能原理
  • LLM的原理理解6-10:6、前馈步骤7、使用向量运算进行前馈网络的推理8、注意力层和前馈层有不同的功能9、语言模型的训练方式10、GPT-3的惊人性能
  • leetcode 面试150之 156.LUR 缓存
  • c++中mystring运算符重载
  • 韩顺平 一周学会Linux | Linux 实操篇-实用指令
  • Python知识点精汇:集合篇精解!
  • 【大数据技术与开发实训】携程景点在线评论分析
  • HTMLCSS:翻书加载效果
  • 解!决!vscode!Path Intellisense 失效!不起作用问题!!
  • 机器学习实战笔记34-38:gridsearchcv的进阶使用,无监督学习:kmeans、DBSCAN
  • web网络安全系统