百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

互联网大厂后端必看!Spring Boot 如何实现高并发抢券逻辑?

mhr18 2025-05-21 15:13 3 浏览 0 评论

在当今电商、本地生活服务等行业的各类促销活动里,高并发抢券的场景极为常见。就拿双 11、618 购物节来说,平台发放的优惠券数量有限,然而参与抢券的用户可能达到百万甚至千万级别,瞬间产生的高并发请求,对系统的稳定性和性能而言,无疑是巨大的挑战。Spring Boot 作为当下后端开发中广泛运用的框架,凭借快速开发、简化配置等优势,成为实现高并发抢券逻辑的热门之选。不过,要想在高并发场景中充分发挥其最大效能,还需深入研究并合理运用各种技术手段。

分布式锁方案

分布式锁是解决高并发抢券问题的常用策略之一,借助 Redis 实现的具体步骤如下:

引入 Redis 依赖:在 Spring Boot 项目的 pom.xml 文件里添加 Redis 相关依赖,比如:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置 Redis 连接:在 application.properties 或 application.yml 文件中设置 Redis 服务器的地址、端口等信息。以 application.yml 为例:

spring:
  redis:
    host: your - redis - host
    port: 6379
    password: your - redis - password

编写获取锁的代码:利用 Redis 的 SET 命令结合 NX 和 EX 参数来尝试获取锁。

public boolean tryGetLock(RedisTemplate<String, String> redisTemplate, String lockKey, String uniqueValue, int expireTime) {
    return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
        JedisCommands commands = (JedisCommands) connection.getNativeConnection();
        String result = commands.set(lockKey, uniqueValue, "NX", "EX", expireTime);
        return "OK".equals(result);
    });
}

这里 lockKey 是锁的唯一标识,uniqueValue 是每个请求的唯一值(比如可以用 UUID 生成),用于防止误删锁,expireTime 是锁的过期时间,单位为秒。

执行业务逻辑并释放锁:当成功获取到锁后,执行优惠券库存扣减、订单生成等核心业务逻辑。完成后,通过 DEL 命令释放锁。

public void releaseLock(RedisTemplate<String, String> redisTemplate, String lockKey, String uniqueValue) {
    String script = "if redis.call('GET', KEYS(1)) == ARGV(1) then return redis.call('DEL', KEYS(1)) else return 0 end";
    redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList(lockKey), uniqueValue);
}

这里运用 Lua 脚本确保判断锁的持有者和释放锁的操作具有原子性,避免在高并发下出现误释放其他线程锁的情况。

这种方式能够保证同一时间仅有一个线程或请求可以执行关键业务逻辑,有效防止超卖现象的发生。但它也存在一些问题,例如在高并发时,大量请求争抢锁会形成性能瓶颈,而且要是锁的过期时间设置不合理,还可能出现锁提前释放导致的并发问题。

利用 Redis 原子操作特性方案

利用 Redis 的原子操作特性实现高并发抢券的具体步骤如下:

初始化库存到 Redis:在活动开始前,将优惠券库存数量设置为 Redis 的一个数值型 Key。可以通过 Redis 客户端工具,或者在 Spring Boot 代码中进行设置。假设 couponStockKey 是代表优惠券库存的 Key,stock 是初始库存数量,代码如下:

redisTemplate.opsForValue().set(couponStockKey, String.valueOf(stock));

编写抢券扣减库存代码:使用 Redis 的 decr 命令进行库存扣减。在 Spring Boot 中,通过 RedisTemplate 执行 decr 操作的代码如下:

public boolean tryRobCoupon(RedisTemplate<String, String> redisTemplate, String couponStockKey) {
    return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
        JedisCommands commands = (JedisCommands) connection.getNativeConnection();
        Long result = commands.decr(couponStockKey);
        return result >= 0;
    });
}

该方法执行 decr 操作后,如果返回值大于等于 0,说明扣减成功,有券可抢;如果返回值小于 0,说明库存已耗尽。

处理可能的误扣情况:由于多线程并发执行 decr 可能出现库存扣成负数的现象,所以在扣减前可以先通过 GET 命令查询库存,判断是否还有剩余。同时,如果出现误扣,可以通过 incr 命令进行库存回滚。

public boolean checkAndRobCoupon(RedisTemplate<String, String> redisTemplate, String couponStockKey) {
    String stockStr = redisTemplate.opsForValue().get(couponStockKey);
    if (stockStr == null) {
        return false;
    }
    int stock = Integer.parseInt(stockStr);
    if (stock > 0) {
        boolean success = tryRobCoupon(redisTemplate, couponStockKey);
        if (!success) {
            // 出现误扣,进行回滚
            redisTemplate.opsForValue().increment(couponStockKey);
        }
        return success;
    }
    return false;
}

结合 Lua 脚本和 Redis 原子操作方案

结合 Lua 脚本和 Redis 的原子操作实现抢券逻辑的具体步骤如下:

编写 Lua 脚本:将抢券的核心逻辑封装在 Lua 脚本中,下面是一个实现先查询库存再扣减,并添加用户限制逻辑的 Lua 脚本示例:

local stockKey = KEYS[1]
local userSetKey = ARGV[1]
local userId = ARGV[2]

local stock = tonumber(redis.call('GET', stockKey))
if stock <= 0 then
    return 0
end

local isRobbed = redis.call('SISMEMBER', userSetKey, userId)
if isRobbed == 1 then
    return -1
end

redis.call('DECR', stockKey)
redis.call('SADD', userSetKey, userId)
return 1

这个脚本中,KEYS [1] 代表优惠券库存的 Key,ARGV [1] 是存储已抢券用户的 Set 集合的 Key,ARGV [2] 是当前抢券用户的标识。脚本首先查询库存,如果库存不足返回 0;然后检查用户是否已经抢过券,如果已抢过返回 - 1;否则扣减库存并将用户标识加入已抢券用户集合,最后返回 1 表示抢券成功。

在 Spring Boot 中执行 Lua 脚本:使用 RedisTemplate 执行 Lua 脚本。通过 RedisTemplate 的 execute 方法,传入 Lua 脚本和相关参数。

public int executeLuaScript(RedisTemplate<String, String> redisTemplate, String stockKey, String userSetKey, String userId) {
    String luaScript = "local stockKey = KEYS[1]\n" +
            "local userSetKey = ARGV[1]\n" +
            "local userId = ARGV[2]\n" +
            "\n" +
            "local stock = tonumber(redis.call('GET', stockKey))\n" +
            "if stock <= 0 then\n" +
            "    return 0\n" +
            "end\n" +
            "\n" +
            "local isRobbed = redis.call('SISMEMBER', userSetKey, userId)\n" +
            "if isRobbed == 1 then\n" +
            "    return -1\n" +
            "end\n" +
            "\n" +
            "redis.call('DECR', stockKey)\n" +
            "redis.call('SADD', userSetKey, userId)\n" +
            "return 1";
    DefaultRedisScript<Integer> redisScript = new DefaultRedisScript<>(luaScript, Integer.class);
    List<String> keys = Collections.singletonList(stockKey);
    List<String> args = Arrays.asList(userSetKey, userId);
    return redisTemplate.execute(redisScript, keys, args);
}

在实际的互联网大厂项目中,实现高并发抢券功能,除了要关注上述核心技术方案外,还需要考虑系统的整体架构设计、性能优化、容灾备份等多个方面。例如,可以采用负载均衡技术将请求均匀分配到多个后端服务器,使用缓存来减轻数据库压力,设计合理的降级和熔断策略以应对突发流量高峰等。希望本文介绍的 Spring Boot 结合 Redis 实现高并发抢券的方法,能为各位互联网大厂后端开发人员在实际项目中提供有效的参考和帮助。让我们一起在高并发的技术浪潮中,打造出更加稳定、高效的系统。

相关推荐

互联网大厂后端必看!Spring Boot 如何实现高并发抢券逻辑?

在当今电商、本地生活服务等行业的各类促销活动里,高并发抢券的场景极为常见。就拿双11、618购物节来说,平台发放的优惠券数量有限,然而参与抢券的用户可能达到百万甚至千万级别,瞬间产生的高并发请求,...

高并发秒杀系统的解决方案

面对秒杀活动的高并发压力,系统需要从前端到后端全面优化。一、前端三板斧是核心:加机器扩容:直接增加服务器数量,用「人海战术」扛住流量峰值。静态化页面:把图片、文字等固定内容提前保存成静态文件,通过CD...

Quarkus集成Redis缓存加速与高并发事务避坑指南

一、为什么你的微服务需要Redis?在日均百万级请求的电商场景中,某核心接口响应时间从200ms降至12ms,秘诀在于合理使用Redis缓存。但在高并发场景下,错误的事务处理曾导致3000笔订单超卖,...

网络投票系统,Redis如何抗住瞬间高并发?

咱们现在在网上参与个投票活动,简直是家常便饭!无论是给喜欢的选秀爱豆打call,还是评选“年度优秀员工”,亦或是参与某个社会热点话题的民意调查,动动手指,投出自己神圣的一票,简单又方便。但你有没有想过...

一起挖矿病毒事件的深度分析,结果你竟想不到

起因朋友公司遇到了一起挖矿病毒事件,找我帮忙看看。入侵分析基本信息检查当我登录服务器做检测时,top回显并未发现异常进程:但是在crontab中发现一条异常的定时任务:通过访问定时任务中的url,发现...

Redis 分布式锁的续期与脑裂问题解决方案

Redis分布式锁的续期与脑裂问题解决方案分布式锁在高并发场景中至关重要,但使用Redis实现时会面临两个关键挑战:锁续期和脑裂问题。以下是详细解决方案:一、锁续期问题解决方案1.自动续期机制...

Redis的“兄弟姐妹”们:Memcached、MongoDB,它们有何不同?

咱们常说“一个好汉三个帮”。Redis虽然在很多领域都表现抢眼,但它也不是一个人在战斗!在广阔的NoSQL(NotOnlySQL,泛指非关系型数据库)世界里,Redis还有不少“兄弟姐妹”。它们各...

阿里淘外商业化广告工程架构实践

大型广告系统工程方面的主要挑战就是海量数据,快速响应,数据实时和高可用度的要求。本次分享介绍了阿里创新事业群智能营销平台在如何构建高性能、高可用、高效率,低成本的广告系统架构方面所做的诸多工作及实践经...

TP-LINK面试真题和答案,您能做对几道?

话说TP-LINK联洲的秋招提前批已经开启很久了,6月份就已经开启了,并且最近已经有人陆陆续续拿到口头Offer了,所以今天就来给大家介绍一下TP-LINK的面试流程和真题及答案解析。秋...

六星教育PHP大神进阶班怎么样?值不值得去听?

点进这篇文章的人可能现在正面临着几个很难选择的问题,比如学PHP要不要报培训班?或者是该怎样选择PHP课程?又或是六星教育的PHP大神进阶班好不好,能不能去?在这里就给你们都一个一个解答了!首先,要...

写给技术工程师的十条精进原则

本文是美团技术专家以自己多年的从业经验总结出了十条精进原则,包括个人层面、团队层面和效能层面,希望也能够给视觉算法工程师们带来一些启发,更好地指导行动。作者|云鹏来源|美团技术团队引言时间回到...

谈谈Linux epoll惊群问题的原因和解决方案

近期排查了一个问题,epoll惊群的问题,起初我并不认为这是惊群导致,因为从现象上看,只是体现了CPU不均衡。一共fork了20个Server进程,在请求负载中等的时候,有三四个Server进程呈现出...

PHP培训课程内容都有哪些?PHP培训哪些内容?

作为一门经久不衰的开发语言,php开发工程师一直是很多年轻人选择学习和就业的职业方向,那么PHP培训课程主要学习哪些内容呢?一、企业级开发专题:深入剖析企业实际开发过程,教授最实用的企业级技术PHP7...

依葫芦画瓢,我用Loki画了个Traefik的面板

依葫芦画瓢,我用Loki画了个Traefik的面板前段时间在Loki2.0发布时,更新了一个配套的用LogQL语法绘制Nginx监控面板的Demo。今天小白准备用同样的手法炮制一个基于Traefik日...

揭开微盟百万商家营销大战背后的数据库秘密

又到了双十一、双十二、年终大促季,每年这个时候都是购物狂欢节,不仅促销产品多、种类全、覆盖面广,促销花样也在不断翻新,直播、砍价、优惠券、加价购等,令人眼花缭乱。当全国人民沉浸在买买买的自嗨中无法自拔...

取消回复欢迎 发表评论: