一.一人一单相关首当其冲的是我们要理解秒杀场景为什么要一人一单想象一个极端的场景在我们秒杀场景的时候不选择将全局id加入到锁里此时优惠卷库存1在同一时刻下我此时是线程1线程1抢到了锁在执行下单付款创建订单的业务流程此时线程2也来抢锁这时候我们共用的是同一把锁都是执行秒杀的场景就会出现线程1已经付款了此时优惠卷库存已经-1了正准备创建订单的时候线程2下单了线程2去付款了这时候优惠卷库存又-1这时候会出现严重的并发安全问题。那么如何解决这个问题呢答案是分布式锁键里加上用户id并且使用lua脚本确保原子性。二.分布式锁实现思路利用set nx ex获取锁并设置过期时间保存线程标示释放锁时先判断线程标示是否与自己一致一致则删除锁特性利用set nx满足互斥性利用set ex保证故障时锁依然能释放避免死锁提高安全性利用Redis集群保证高可用和高并发特性1.互斥确保只有一个线程可以获取锁。2.非阻塞尝试获取一次成功返回true失败返回false3.超时释放设置一个锁过期时间超时释放。最佳实践使用 Redis 2.6.12 支持的 SET 命令原子组合参数如 SET lock_key unique_value NX EX 30 NX 表示仅当键不存在时设置 EX 30 表示过期时间为30秒。核心在于# 添加锁利用setnx的互斥特性 SETNX lock thread1# 添加锁过期时间避免服务宕机引起的死锁 EXPIRE lock 10但是当我们释放锁时会出现一个极端问题分布式锁的原子性问题线程1现在持有锁之后在执行业务逻辑过程中他正准备删除锁而且已经走到了条件判断的过程中比如他已经拿到了当前这把锁确实是属于他自己的正准备删除锁但是此时他的锁到期了那么此时线程2进来但是线程1他会接着往后执行当他卡顿结束后他直接就会执行删除锁那行代码相当于条件判断并没有起到作用这就是删锁时的原子性问题之所以有这个问题是因为线程1的拿锁比锁删锁实际上并不是原子性的。此时就需要使用到lua脚本来保证原子性了。三.Lua脚本改造分布式锁https://www.runoob.com/lua/lua-tutorial.htmledis提供了Lua脚本功能在一个脚本中编写多条Redis命令确保多条命令执行时的原子性。预扣库存代码// Redis 原子扣减Lua 脚本保证原子性 String script if redis.call(get, KEYS[1]) tonumber(ARGV[1]) then return redis.call(decrby, KEYS[1], ARGV[1]) else return -1 end; Long result stringRedisTemplate.execute( new DefaultRedisScript(script, Long.class), Collections.singletonList(SECKILL_STOCK_KEY voucherId), 1 ); if (result 0) { return Result.fail(库存不足); } // 异步下单发送消息到 MQ // mqProducer.send(new OrderMessage(userId, voucherId)); return Result.ok(排队中请稍后查看订单);