目录
[实战篇 - 01. 优惠券秒杀 - 全局唯一 ID](#实战篇 - 01. 优惠券秒杀 - 全局唯一 ID)
[实战篇 - 02. 优惠券秒杀 - Redis 实现全局唯一 id](#实战篇 - 02. 优惠券秒杀 - Redis 实现全局唯一 id)
[实战篇 - 03. 优惠券秒杀 - 添加优惠券](#实战篇 - 03. 优惠券秒杀 - 添加优惠券)
[实战篇 - 04. 优惠券秒杀 - 实现秒杀下单](#实战篇 - 04. 优惠券秒杀 - 实现秒杀下单)
[实战篇 - 05. 优惠券秒杀 - 库存超卖问题分析](#实战篇 - 05. 优惠券秒杀 - 库存超卖问题分析)
[实战篇 - 06. 优惠券秒杀 - 乐观锁解决超卖](#实战篇 - 06. 优惠券秒杀 - 乐观锁解决超卖)
[实战篇 - 07. 优惠券秒杀 - 实现一人一单功能](#实战篇 - 07. 优惠券秒杀 - 实现一人一单功能)
问题:下图在有@Transaction的方法使用this调用别的方法有没有问题?
问题:AopContext.currentProxy()作用及步骤?
[实战篇 - 08. 优惠券秒杀 - 集群下的线程并发安全问题](#实战篇 - 08. 优惠券秒杀 - 集群下的线程并发安全问题)
[实战篇 - 09. 分布式锁 - 基本原理和不同实现方式对比](#实战篇 - 09. 分布式锁 - 基本原理和不同实现方式对比)
[实战篇 - 10. 分布式锁 - Redis 的分布式锁实现思路](#实战篇 - 10. 分布式锁 - Redis 的分布式锁实现思路)
[实战篇 - 11. 分布式锁 - 实现 Redis 分布式锁版本 1](#实战篇 - 11. 分布式锁 - 实现 Redis 分布式锁版本 1)
[实战篇 - 12. 分布式锁 - Redis 分布式锁误删问题](#实战篇 - 12. 分布式锁 - Redis 分布式锁误删问题)
[实战篇 - 13. 分布式锁 - 解决 Redis 分布式锁误删问题](#实战篇 - 13. 分布式锁 - 解决 Redis 分布式锁误删问题)
[实战篇 - 14. 分布式锁 - 分布式锁的原子性问题](#实战篇 - 14. 分布式锁 - 分布式锁的原子性问题)
[实战篇 - 15. 分布式锁 - Lua 脚本解决多条命令原子性问题](#实战篇 - 15. 分布式锁 - Lua 脚本解决多条命令原子性问题)
[实战篇 - 16. 分布式锁 - Java 调用 lua 脚本改造分布式锁](#实战篇 - 16. 分布式锁 - Java 调用 lua 脚本改造分布式锁)
[实战篇 - 17. 分布式锁 - Redisson 功能介绍](#实战篇 - 17. 分布式锁 - Redisson 功能介绍)
[实战篇 - 18. 分布式锁 - Redisson 快速入门](#实战篇 - 18. 分布式锁 - Redisson 快速入门)
[实战篇 - 19. 分布式锁 - Redisson 的可重入锁原理](#实战篇 - 19. 分布式锁 - Redisson 的可重入锁原理)
[实战篇 - 20. 分布式锁 - Redisson的锁重试和WatchDog机制](#实战篇 - 20. 分布式锁 - Redisson的锁重试和WatchDog机制)
[实战篇 - 21. 分布式锁 - Redisson 的 multiLock 原理](#实战篇 - 21. 分布式锁 - Redisson 的 multiLock 原理)
[实战篇 - 22. 秒杀优化 - 异步秒杀思路](#实战篇 - 22. 秒杀优化 - 异步秒杀思路)
问题:如何在Redis中完成对秒杀库存的判断和校验一人一单?
[实战篇 - 23. 秒杀优化 - 基于 Redis 完成秒杀资格判断](#实战篇 - 23. 秒杀优化 - 基于 Redis 完成秒杀资格判断)
[后面的 key value key value...](#后面的 key value key value...)
[实战篇 - 24. 秒杀优化 - 基于阻塞队列实现秒杀异步下单](#实战篇 - 24. 秒杀优化 - 基于阻塞队列实现秒杀异步下单)
[实战篇 - 25.Redis 消息队列 - 认识消息队列](#实战篇 - 25.Redis 消息队列 - 认识消息队列)
[实战篇 - 26.Redis 消息队列 - 基于 List 实现消息队列](#实战篇 - 26.Redis 消息队列 - 基于 List 实现消息队列)
[实战篇 - 27.Redis 消息队列 - PubSub 实现消息队列](#实战篇 - 27.Redis 消息队列 - PubSub 实现消息队列)
[实战篇 - 28.Redis 消息队列 - Stream 的单消费模式](#实战篇 - 28.Redis 消息队列 - Stream 的单消费模式)
[实战篇 - 29.Redis 消息队列 - Stream 的消费者组模式](#实战篇 - 29.Redis 消息队列 - Stream 的消费者组模式)
[实战篇 - 30.Redis 消息队列 - 基于 Stream 消息队列实现异步秒杀](#实战篇 - 30.Redis 消息队列 - 基于 Stream 消息队列实现异步秒杀)
实战篇 - 01. 优惠券秒杀 - 全局唯一 ID



问题:解释下面代码?
increment(key):Redis 的原子自增命令 (对应 Redis 原生INCR命令),作用是:
- 如果 key 不存在:自动创建 key,值设为 1,返回 1。
- 如果 key 存在:把 key 对应的值 + 1,返回新值。
- 原子性保证 :Redis 单线程执行,
INCR是原子操作,高并发下不会出现计数错误,完美解决分布式自增问题。
javapackage com.hmdp.utils; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; //id生成器 @Component public class RedisIdWorker { /** * 开始时间戳 */ private static final long BEGIN_TIMESTAMP = 1640995200L; /** * 序列号的位数 */ private static final int COUNT_BITS = 32; private StringRedisTemplate stringRedisTemplate; public RedisIdWorker(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } public long nextId(String keyPrefix) { // 1.生成时间戳 LocalDateTime now = LocalDateTime.now(); long nowSecond = now.toEpochSecond(ZoneOffset.UTC); long timestamp = nowSecond - BEGIN_TIMESTAMP; // 2.生成序列号 // 2.1.获取当前日期,精确到天 String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd")); // 2.2.自增长 long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date); // 3.拼接并返回 return timestamp << COUNT_BITS | count; } }

实战篇 - 02. 优惠券秒杀 - Redis 实现全局唯一 id
实战篇 - 03. 优惠券秒杀 - 添加优惠券


实战篇 - 04. 优惠券秒杀 - 实现秒杀下单

实战篇 - 05. 优惠券秒杀 - 库存超卖问题分析


问题:乐观锁有哪两种实现方法?
CAS使用==弊端:成功率低
解决方法:只要判断库存>0即可

实战篇 - 06. 优惠券秒杀 - 乐观锁解决超卖
实战篇 - 07. 优惠券秒杀 - 实现一人一单功能

问题:下图在有@Transaction的方法使用this调用别的方法有没有问题?
问题:AopContext.currentProxy()作用及步骤?
AopContext.currentProxy () 获取的是当前 Spring 容器中的「代理对象」(Proxy Object),而不是你原本创建的目标对象 (Target Object)。
使用它需要引入依赖并在启动类上加以下注解
问题:获取的代理对象是什么?
实战篇 - 08. 优惠券秒杀 - 集群下的线程并发安全问题


实战篇 - 09. 分布式锁 - 基本原理和不同实现方式对比


问题:上图怎么保证添加锁和添加过期时间的原子性?
实战篇 - 10. 分布式锁 - Redis 的分布式锁实现思路

实战篇 - 11. 分布式锁 - 实现 Redis 分布式锁版本 1
实战篇 - 12. 分布式锁 - Redis 分布式锁误删问题



实战篇 - 13. 分布式锁 - 解决 Redis 分布式锁误删问题

实战篇 - 14. 分布式锁 - 分布式锁的原子性问题

实战篇 - 15. 分布式锁 - Lua 脚本解决多条命令原子性问题




实战篇 - 16. 分布式锁 - Java 调用 lua 脚本改造分布式锁



问题:解释下面代码?
java// 调用lua脚本 stringRedisTemplate.execute( UNLOCK_SCRIPT, Collections.singletonList(KEY_PREFIX + name), ID_PREFIX + Thread.currentThread().getId());
stringRedisTemplate.execute(...)执行 Redis 命令的通用方法 专门用来执行 Lua 脚本
UNLOCK_SCRIPT提前写好的 Lua 解锁脚本
java//声明,方便后面调用Lua脚本 private static final DefaultRedisScript<Long> UNLOCK_SCRIPT; static { UNLOCK_SCRIPT = new DefaultRedisScript<>(); UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua")); UNLOCK_SCRIPT.setResultType(Long.class); }
Lua//Lua脚本 -- 比较线程标示与锁中的标示是否一致 if(redis.call('get', KEYS[1]) == ARGV[1]) then -- 释放锁 del key return redis.call('del', KEYS[1]) end return 0
Collections.singletonList(KEY_PREFIX + name)传给 Lua 脚本的 KEYS(Redis 的 key)
KEY_PREFIX + name= 锁的 key例如:lock:order:101
Collections.singletonList()= 转成单元素列表因为 Lua 脚本要求 KEYS 必须是列表格式
ID_PREFIX + Thread.currentThread().getId()传给 Lua 脚本的 ARGV(参数值)
Thread.currentThread().getId():当前线程 ID
ID_PREFIX:前缀,避免重复组合起来 = 当前线程的唯一标识 例如:
uuid-10086作用:用来判断 Redis 里存的锁,是不是当前线程加的
实战篇 - 17. 分布式锁 - Redisson 功能介绍


实战篇 - 18. 分布式锁 - Redisson 快速入门


实战篇 - 19. 分布式锁 - Redisson 的可重入锁原理

问题:exists和Hexists有什么区别?
实战篇 - 20. 分布式锁 - Redisson的锁重试和WatchDog机制
问题:redission是怎么解决上图四个问题的?
01 问题:不可重入(同一线程无法多次获取同一把锁)
问题本质
普通
SETNX锁是「一把锁一个值」,同一个线程第二次加锁时,SETNX会直接失败,无法重入,导致死锁。Redisson 解决方案:可重入锁(基于 Hash 结构存储)
Redisson 用 Hash 数据结构 存储锁信息,结构如下:
Key :锁的名称(如
lock:order:1001)Field :当前线程的唯一标识(
UUID + 线程ID,全局唯一)Value:重入次数(计数器)
加锁逻辑(Lua 脚本原子实现):
先判断锁是否存在:
不存在 → 直接设置 Hash 结构,重入次数设为
1,设置过期时间,加锁成功存在 → 判断当前线程标识是否在 Hash 中:
存在 → 重入次数
+1,刷新过期时间,加锁成功(可重入)不存在 → 加锁失败
解锁时,重入次数
-1,直到次数为0才真正删除锁。核心优势:
同一个线程可以多次获取同一把锁,不会死锁,完美解决不可重入问题。
02 问题:不可重试(获取锁只尝试一次就返回 false,无重试机制)
问题本质
普通
SETNX锁加锁失败直接返回,无法自动重试,高并发下容易导致业务失败。Redisson 解决方案:自旋重试 + 可配置等待时间
Redisson 提供了
lock()/tryLock()两种加锁方式,支持灵活的重试机制:
lock():阻塞式加锁,底层通过「自旋 + 信号量」不断重试,直到获取到锁为止,不会直接返回失败
tryLock(long waitTime, long leaseTime, TimeUnit unit):
waitTime:最大等待时间(在这个时间内不断重试加锁)
leaseTime:锁的持有时间超时未获取到锁才返回
false,支持自定义重试策略底层实现:
通过 订阅锁释放的消息 实现高效重试:
加锁失败时,订阅锁释放的 Pub/Sub 消息
等待锁释放后,被唤醒并重新尝试加锁
避免了无效的空转,大幅提升性能,解决了不可重试问题
03 问题:超时释放(业务执行过长导致锁提前释放,有安全隐患)
问题本质
普通
SETNX锁设置固定过期时间,若业务执行时间超过过期时间,锁会自动释放,导致其他线程加锁成功,出现并发安全问题。Redisson 解决方案:看门狗(WatchDog)自动续期机制
Redisson 内置了 看门狗(WatchDog) 线程,核心逻辑:
若用户未手动指定
leaseTime(锁过期时间),默认锁过期时间为30s看门狗每隔
30s / 3 = 10s检查一次:
若当前线程仍持有锁 → 自动将锁的过期时间重置为
30s(续期)若线程已释放锁 → 停止续期
只要业务未执行完成,锁就会一直续期,不会提前释放;业务执行完成后,主动释放锁,看门狗自动停止。
核心优势:
彻底解决了「业务超时导致锁提前释放」的安全隐患,同时避免了死锁(业务异常时,锁到期自动释放)。
04 问题:主从一致性(Redis 主从集群,主从同步延迟导致锁失效)
问题本质
普通
SETNX锁只在主节点加锁,若主节点宕机,主从同步未完成,从节点升为主节点后,锁数据丢失,导致多个线程同时加锁成功,锁完全失效。Redisson 解决方案:RedLock 红锁 + 多实例锁(MultiLock)
Redisson 提供了两种方案解决主从一致性问题:
方案 1:RedLock(红锁,官方推荐)
基于 Redis 官方的 RedLock 算法,核心逻辑:
向 N 个独立的 Redis 主节点(至少 3 个,互不相关)同时加锁
只有当 超过 N/2 个节点加锁成功 ,且总耗时小于锁过期时间,才认为加锁成功
解锁时,向所有节点发送解锁请求
即使单个节点宕机,只要多数节点锁有效,锁就不会失效,彻底解决主从同步延迟问题。
方案 2:MultiLock(多实例锁)
针对 Redis 主从集群 / 哨兵集群,将多个主节点的锁组合成一把「联锁」,只有所有节点加锁成功,锁才生效,保证锁的全局一致性。
补充:主从环境下的优化
即使使用普通的
RLock,Redisson 也会优先在主节点操作,同时通过「锁的唯一标识 + 原子操作」保证锁的安全性,最大程度降低主从同步延迟的影响。
问题 核心痛点 Redisson 解决方案 核心原理 不可重入 同一线程无法多次加锁 可重入锁(Hash 结构存储) 用 Hash 存储线程标识 + 重入次数,支持多次加锁 不可重试 加锁失败直接返回,无重试 自旋重试 + 可配置等待时间 阻塞式加锁 + Pub/Sub 消息唤醒,支持自定义重试 超时释放 业务超时导致锁提前释放 看门狗(WatchDog)自动续期 定时检查锁状态,业务未完成则自动续期 主从一致性 主从同步延迟导致锁失效 RedLock 红锁 / MultiLock 多实例锁 多节点独立加锁,多数节点成功才生效,保证全局一致
问题:这里面的ttl是什么?

实战篇 - 21. 分布式锁 - Redisson 的 multiLock 原理


实战篇 - 22. 秒杀优化 - 异步秒杀思路

问题:如何在Redis中完成对秒杀库存的判断和校验一人一单?
实战篇 - 23. 秒杀优化 - 基于 Redis 完成秒杀资格判断

问题:下图Lua代码表示什么?
Lua
-- 1.参数列表
-- 1.1.优惠券id
local voucherId = ARGV[1]
-- 1.2.用户id
local userId = ARGV[2]
-- 1.3.订单id
local orderId = ARGV[3]
-- 2.数据key
-- 2.1.库存key
local stockKey = 'seckill:stock:' .. voucherId
-- 2.2.订单key
local orderKey = 'seckill:order:' .. voucherId
-- 3.脚本业务
-- 3.1.判断库存是否充足 get stockKey
if(tonumber(redis.call('get', stockKey)) <= 0) then
-- 3.2.库存不足,返回1
return 1
end
-- 3.2.判断用户是否下单 SISMEMBER orderKey userId
if(redis.call('sismember', orderKey, userId) == 1) then
-- 3.3.存在,说明是重复下单,返回2
return 2
end
-- 3.4.扣库存 incrby stockKey -1
redis.call('incrby', stockKey, -1)
-- 3.5.下单(保存用户)sadd orderKey userId
redis.call('sadd', orderKey, userId)
-- 3.6.发送消息到队列中, XADD stream.orders * k1 v1 k2 v2 ...
redis.call('xadd', 'stream.orders', '*', 'userId', userId, 'voucherId', voucherId, 'id', orderId)
return 0
问题:解释下面代码?
Lua-- 3.6.发送消息到队列中, XADD stream.orders * k1 v1 k2 v2 ... redis.call('xadd', 'stream.orders', '*', 'userId', userId, 'voucherId', voucherId, 'id', orderId)
redis.call()Lua 脚本里调用 Redis 命令的固定写法
xaddRedis Stream 消息队列 的 添加消息命令= 往消息队列里扔一条消息
stream.orders消息队列的名称 你可以理解为:
订单专用消息队列
*自动生成消息 IDRedis 自动生成唯一 ID,不用我们自己传
后面的
key value key value...要发送的消息内容(订单数据):
userId:用户 IDvoucherId:优惠券 IDid:订单 ID
问题:这个intValue是干嘛的?
Lua// 1.执行lua脚本 Long result = stringRedisTemplate.execute( SECKILL_SCRIPT, Collections.emptyList(), voucherId.toString(), userId.toString(), String.valueOf(orderId) ); int r = result.intValue();
intValue()就是把 Lua 返回的 Long 类型数字,转成 int 类型数字,方便后面做判断,只是类型转换!
实战篇 - 24. 秒杀优化 - 基于阻塞队列实现秒杀异步下单
问题:实现阻塞队列代码?
java//线程池 private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor(); private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024 * 1024); private class VoucherOrderHandler implements Runnable{ @Override public void run() { while (true){ try { // 1.获取队列中的订单信息 VoucherOrder voucherOrder = orderTasks.take(); // 2.创建订单 createVoucherOrder(voucherOrder); } catch (Exception e) { log.error("处理订单异常", e); } } } }
实战篇 - 25.Redis 消息队列 - 认识消息队列

实战篇 - 26.Redis 消息队列 - 基于 List 实现消息队列


实战篇 - 27.Redis 消息队列 - PubSub 实现消息队列


实战篇 - 28.Redis 消息队列 - Stream 的单消费模式




实战篇 - 29.Redis 消息队列 - Stream 的消费者组模式






问题:下面代码中>和0区别?
>= 只消费新消息,用于正常实时消费0= 消费历史待处理消息,用于重试 / 故障恢复
问题:为什么有isEmpty()还需要null判断?
实战篇 - 30.Redis 消息队列 - 基于 Stream 消息队列实现异步秒杀

问题:解释下面代码?
末尾页
本文摘要: 本文详细讲解了分布式系统中优惠券秒杀的核心技术实现。主要内容包括:1)使用Redis原子自增实现全局唯一ID生成;2)通过乐观锁解决库存超卖问题;3)实现一人一单功能;4)Redis分布式锁的实现与优化,包括Lua脚本保证原子性和Redisson的可重入锁;5)秒杀优化方案,包括异步秒杀思路、Redis资格判断和消息队列实现;6)Redis消息队列的多种实现方式对比。文章通过代码示例详细解析了各项技术的实现原理和解决方案,涵盖了分布式系统开发中的典型并发问题和解决思路。






























