redis(day03-优惠券秒杀)

张开发
2026/4/11 23:25:09 15 分钟阅读

分享文章

redis(day03-优惠券秒杀)
目录实战篇 - 01. 优惠券秒杀 - 全局唯一 ID问题:解释下面代码实战篇 - 02. 优惠券秒杀 - Redis 实现全局唯一 id实战篇 - 03. 优惠券秒杀 - 添加优惠券实战篇 - 04. 优惠券秒杀 - 实现秒杀下单实战篇 - 05. 优惠券秒杀 - 库存超卖问题分析问题:乐观锁有哪两种实现方法实战篇 - 06. 优惠券秒杀 - 乐观锁解决超卖实战篇 - 07. 优惠券秒杀 - 实现一人一单功能问题:下图在有Transaction的方法使用this调用别的方法有没有问题?问题:AopContext.currentProxy()作用及步骤实战篇 - 08. 优惠券秒杀 - 集群下的线程并发安全问题实战篇 - 09. 分布式锁 - 基本原理和不同实现方式对比​编辑问题:上图怎么保证添加锁和添加过期时间的原子性实战篇 - 10. 分布式锁 - Redis 的分布式锁实现思路实战篇 - 11. 分布式锁 - 实现 Redis 分布式锁版本 1实战篇 - 12. 分布式锁 - Redis 分布式锁误删问题实战篇 - 13. 分布式锁 - 解决 Redis 分布式锁误删问题实战篇 - 14. 分布式锁 - 分布式锁的原子性问题实战篇 - 15. 分布式锁 - Lua 脚本解决多条命令原子性问题实战篇 - 16. 分布式锁 - Java 调用 lua 脚本改造分布式锁问题:解释下面代码?实战篇 - 17. 分布式锁 - Redisson 功能介绍实战篇 - 18. 分布式锁 - Redisson 快速入门实战篇 - 19. 分布式锁 - Redisson 的可重入锁原理问题:exists和Hexists有什么区别?实战篇 - 20. 分布式锁 - Redisson的锁重试和WatchDog机制问题:redission是怎么解决上图四个问题的问题:这里面的ttl是什么?实战篇 - 21. 分布式锁 - Redisson 的 multiLock 原理实战篇 - 22. 秒杀优化 - 异步秒杀思路问题:如何在Redis中完成对秒杀库存的判断和校验一人一单实战篇 - 23. 秒杀优化 - 基于 Redis 完成秒杀资格判断问题:下图Lua代码表示什么问题:解释下面代码redis.call()xaddstream.orders*后面的 key value key value...问题:这个intValue是干嘛的实战篇 - 24. 秒杀优化 - 基于阻塞队列实现秒杀异步下单问题:实现阻塞队列代码​编辑实战篇 - 25.Redis 消息队列 - 认识消息队列实战篇 - 26.Redis 消息队列 - 基于 List 实现消息队列实战篇 - 27.Redis 消息队列 - PubSub 实现消息队列实战篇 - 28.Redis 消息队列 - Stream 的单消费模式实战篇 - 29.Redis 消息队列 - Stream 的消费者组模式问题:下面代码中和0区别问题:为什么有isEmpty()还需要null判断实战篇 - 30.Redis 消息队列 - 基于 Stream 消息队列实现异步秒杀问题:解释下面代码末尾页实战篇 - 01. 优惠券秒杀 - 全局唯一 ID问题:解释下面代码increment(key)Redis 的原子自增命令对应 Redis 原生INCR命令作用是如果 key 不存在自动创建 key值设为 1返回 1。如果 key 存在把 key 对应的值 1返回新值。原子性保证Redis 单线程执行INCR是原子操作高并发下不会出现计数错误完美解决分布式自增问题。package 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 脚本改造分布式锁问题:解释下面代码?// 调用lua脚本 stringRedisTemplate.execute( UNLOCK_SCRIPT, Collections.singletonList(KEY_PREFIX name), ID_PREFIX Thread.currentThread().getId());1.stringRedisTemplate.execute(...)执行 Redis 命令的通用方法专门用来执行Lua 脚本2.UNLOCK_SCRIPT提前写好的 Lua 解锁脚本//声明,方便后面调用Lua脚本 private static final DefaultRedisScriptLong UNLOCK_SCRIPT; static { UNLOCK_SCRIPT new DefaultRedisScript(); UNLOCK_SCRIPT.setLocation(new ClassPathResource(unlock.lua)); UNLOCK_SCRIPT.setResultType(Long.class); }//Lua脚本 -- 比较线程标示与锁中的标示是否一致 if(redis.call(get, KEYS[1]) ARGV[1]) then -- 释放锁 del key return redis.call(del, KEYS[1]) end return 03.Collections.singletonList(KEY_PREFIX name)传给 Lua 脚本的KEYSRedis 的 keyKEY_PREFIX name 锁的 key例如lock:order:101Collections.singletonList() 转成单元素列表因为 Lua 脚本要求 KEYS 必须是列表格式4.ID_PREFIX Thread.currentThread().getId()传给 Lua 脚本的ARGV参数值Thread.currentThread().getId()当前线程 IDID_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:1001Field当前线程的唯一标识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 红锁 多实例锁MultiLockRedisson 提供了两种方案解决主从一致性问题方案 1RedLock红锁官方推荐基于 Redis 官方的 RedLock 算法核心逻辑向N 个独立的 Redis 主节点至少 3 个互不相关同时加锁只有当超过 N/2 个节点加锁成功且总耗时小于锁过期时间才认为加锁成功解锁时向所有节点发送解锁请求即使单个节点宕机只要多数节点锁有效锁就不会失效彻底解决主从同步延迟问题。方案 2MultiLock多实例锁针对 Redis 主从集群 / 哨兵集群将多个主节点的锁组合成一把「联锁」只有所有节点加锁成功锁才生效保证锁的全局一致性。补充主从环境下的优化即使使用普通的RLockRedisson 也会优先在主节点操作同时通过「锁的唯一标识 原子操作」保证锁的安全性最大程度降低主从同步延迟的影响。问题核心痛点Redisson 解决方案核心原理不可重入同一线程无法多次加锁可重入锁Hash 结构存储用 Hash 存储线程标识 重入次数支持多次加锁不可重试加锁失败直接返回无重试自旋重试 可配置等待时间阻塞式加锁 Pub/Sub 消息唤醒支持自定义重试超时释放业务超时导致锁提前释放看门狗WatchDog自动续期定时检查锁状态业务未完成则自动续期主从一致性主从同步延迟导致锁失效RedLock 红锁 / MultiLock 多实例锁多节点独立加锁多数节点成功才生效保证全局一致问题:这里面的ttl是什么?实战篇 - 21. 分布式锁 - Redisson 的 multiLock 原理实战篇 - 22. 秒杀优化 - 异步秒杀思路问题:如何在Redis中完成对秒杀库存的判断和校验一人一单实战篇 - 23. 秒杀优化 - 基于 Redis 完成秒杀资格判断问题:下图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问题:解释下面代码-- 3.6.发送消息到队列中 XADD stream.orders * k1 v1 k2 v2 ... redis.call(xadd, stream.orders, *, userId, userId, voucherId, voucherId, id, orderId)redis.call()Lua 脚本里调用 Redis 命令的固定写法xaddRedisStream 消息队列的添加消息命令 往消息队列里扔一条消息stream.orders消息队列的名称你可以理解为订单专用消息队列*自动生成消息 IDRedis 自动生成唯一 ID不用我们自己传后面的key value key value...要发送的消息内容订单数据userId用户 IDvoucherId优惠券 IDid订单 ID问题:这个intValue是干嘛的// 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. 秒杀优化 - 基于阻塞队列实现秒杀异步下单问题:实现阻塞队列代码//线程池 private static final ExecutorService SECKILL_ORDER_EXECUTOR Executors.newSingleThreadExecutor(); private BlockingQueueVoucherOrder 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实现一人一单功能4Redis分布式锁的实现与优化包括Lua脚本保证原子性和Redisson的可重入锁5秒杀优化方案包括异步秒杀思路、Redis资格判断和消息队列实现6Redis消息队列的多种实现方式对比。文章通过代码示例详细解析了各项技术的实现原理和解决方案涵盖了分布式系统开发中的典型并发问题和解决思路。

更多文章