Redis黑马点评 分布式锁

1.不可重复下单。

java 复制代码
@Override
    public Result seckillVoucher(Long voucherId) {
        //1.查询优惠券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        //2.判断秒杀是否开始
        if(voucher.getBeginTime().isAfter(LocalDateTime.now())){
            //未开始
            return Result.fail("秒杀尚未开始");
        }
        //3.判断秒杀是否结束
        if(voucher.getEndTime().isBefore(LocalDateTime.now())){
            return Result.fail("秒杀已结束");
        }
        //4.库存是否充足
        if (voucher.getStock() < 1){
            return Result.fail("库存不足");
        }
        Long userId = UserHolder.getUser().getId();
        //创建锁
        SimpleRedisLock lock = new SimpleRedisLock("order:"+userId, stringRedisTemplate);
        //6一人一单
        boolean isLock = lock.tryLock(1200L);
        if(!isLock){
            //失败
            return Result.fail("请勿重复下单");
        }
        try {
            //获取代理对象(事务)
            IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();
            return proxy.creatVoucherOrder(voucherId);
        } finally {
            //释放锁
            lock.unlock();
        }

    }

    @Transactional
    public Result creatVoucherOrder(Long voucherId) {
        //6一人一单
        Long userId = UserHolder.getUser().getId();

        //6.1查询订单
        Integer count = query().eq("user_id",userId).eq("voucher_id", voucherId).count();
        //6.2判断是否存在
        if(count > 0){
            return Result.fail("不能重复下单");
        }

        //5.扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock -1")
                .eq("voucher_id", voucherId).gt("stock",0)
                .update();
        if(!success){
            return Result.fail("库存不足");
        }


        //6.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //6.1订单id 用户id 代金券id
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        voucherOrder.setUserId(userId);
        voucherOrder.setVoucherId(voucherId);

        save(voucherOrder);
        //7.返回订单id
        return Result.ok(orderId);
    }

java 复制代码
package com.hmdp.utils;

import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.concurrent.TimeUnit;

public class SimpleRedisLock implements ILock{
    private StringRedisTemplate stringRedisTemplate;
    private String name;

    public SimpleRedisLock( String name,StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.name = name;
    }

    private static final String KEY_PREFIX = "lock:";


    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程标示
        long threadId = Thread.currentThread().getId();
        //获取锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name,threadId+"", timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }

    @Override
    public void unlock() {

    }


}

在 Redis 中使用 Lua 脚本的主要原因是 原子性性能优化。Redis 是一个高性能的键值存储系统,通常用于缓存、消息队列等场景。在这些场景中,确保操作的原子性和高效性是非常重要的。Lua 脚本可以帮助你实现这些目标。以下是具体的原因和好处:

1. 原子性

Redis 的 Lua 脚本执行是原子性的,这意味着在脚本执行期间,其他客户端的请求不会中断脚本的执行。这确保了脚本中的操作序列是不可分割的,从而避免了并发问题。

2. 性能优化

使用 Lua 脚本可以减少网络往返次数。如果你需要在 Redis 中执行多个操作,每次操作都需要与 Redis 服务器进行一次网络交互。通过将这些操作封装在 Lua 脚本中,可以一次性发送脚本并执行,从而减少网络延迟。

3. 复杂逻辑

Lua 脚本可以实现复杂的逻辑,而不需要在客户端进行多次交互。这使得你可以将复杂的业务逻辑封装在 Redis 中,减少客户端的负担。

4. 减少锁的使用

在某些情况下,使用 Lua 脚本可以减少锁的使用。通过将多个操作封装在 Lua 脚本中,可以避免在客户端使用锁来确保操作的原子性。

Lua

java 复制代码
---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by zhaoming.
--- DateTime: 2025/10/27 10:36
---

--比较线程标识与锁中是否一致
if(redis.call('get',KEY[1]) == ARGV[1]) then
    --释放锁
    return redis.call('del',KEY[1])
end
return 0

SimpleRedisLock

java 复制代码
public class SimpleRedisLock implements ILock{
    private StringRedisTemplate stringRedisTemplate;
    private String name;

    public SimpleRedisLock( String name,StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.name = name;
    }

    private static final String KEY_PREFIX = "lock:";
    private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";
    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
    static {
            UNLOCK_SCRIPT = new DefaultRedisScript<>();
            UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
            UNLOCK_SCRIPT.setResultType(Long.class);
    }


    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程标示
        long threadId = Thread.currentThread().getId();
        //获取锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name,threadId+"", timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }

    @Override
    public void unlock() {
        //调用lua脚本
        stringRedisTemplate.execute(UNLOCK_SCRIPT,
                Collections.singletonList(KEY_PREFIX + name),
                ID_PREFIX + Thread.currentThread().getId());

    }
相关推荐
xieliyu.5 分钟前
MySQL 全套入门笔记:基础、库操作、数据类型
数据库·笔记·mysql
lvbinemail8 分钟前
【无标题】
数据库·postgresql·zabbix·监控
小小工匠13 分钟前
Redis - 缓冲区管理:避免溢出引发的“惨案“
redis·性能优化·集群·内存管理·持久化
技术小甜甜16 分钟前
[办公效率] Excel 表格越做越乱,先整理字段、格式还是公式?
数据库·excel·办公效率·数据整理
无心水18 分钟前
17、本地多模态|Qwen-VL离线私有化提取敏感PDF完全指南
人工智能·分布式·架构·openclaw·hermes
Data-Miner19 分钟前
休闲食品行业数据分析平台建设方案,揭秘增长新引擎!
大数据·数据库·数据分析
KKKlucifer21 分钟前
数据分类分级排名解析:三大核心能力决定选型方向
大数据·数据库·分类
fly spider24 分钟前
Spring 原理总览:从启动到请求执行
java·数据库·spring
天天进步201526 分钟前
Python全栈项目--基于Python的数据库管理工具
开发语言·数据库·python
小小工匠30 分钟前
Redis - 缓存与数据库一致性:问题分析与解决方案
redis·缓存·性能优化·消息队列·并发