Redis技术学习|实战项目记录|优惠券秒杀|全局唯一ID+超卖问题+集群导致的并发安全问题+redisson的工作原理+异步处理+消息队列

本文介绍了使用Redis实现分布式锁以优化电商秒杀系统的案例,包括使用Redisson解决并发安全问题、全局唯一ID生成、利用Lua脚本确保事务原子性,以及如何结合SpringBoot、MybatisPlus等框架进行开发。作者反思了在lua脚本方面的学习不足,并提及了消息队列的应用可能带来的优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学习资料声明

黑马程序员的Redis学习视频:黑马程序员Redis入门到实战教程
需要用到的知识:linux(推荐韩顺平老师的教程,学到p30,创建好虚拟机和简单的几个命令就好。)SSM。SpringBoot。
还用到了MybatisPlus(还没学。)

全局唯一ID

在这里插入图片描述
在这里插入图片描述

加锁解决超卖问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

集群:一人一单并发安全问题在这里插入图片描述

有多个JVM,每个JVM都有自己的锁管理。
如何让多个JVM使用一个锁监视器。
在这里插入图片描述
在这里插入图片描述

redis实现分布式锁

获取锁和释放锁 setnx,del。如何实现安全性,设置超时时间。但是NX和EX分开会导致错误,所以用SET替代。
Q1:释放锁加强:增加标识,在释放锁之前进行验证,保证释放的是自己的锁。
为什么会释放别人的锁,因为我们设置的key是基于线程id来构造的,集群情况下会出现线程id一样的情况,所以会出现释放别人锁的情况。因此,使用uuid作为标识。(value的比较)
Q2:这个点没看懂,为什么能在超时释放锁的时候获取锁?redis事务确实不能保证原子性
在这里插入图片描述
lua脚本实现原子化。

redisson分布式锁的原理

在这里插入图片描述
在这里插入图片描述
如何解决主从一致的问题?
方法一,取消主从机制,向所有节点发送获取锁请求,只有从所有节点都得到锁算是真正的获取锁。multilock。联锁
在这里插入图片描述
在这里插入图片描述

优化秒杀功能

在这里插入图片描述
解决思路:设置两个部分,验证资格和写数据库。异步执行。

在这一部分实现的代码:

@Slf4j
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedissonClient redissonClient;

    private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
    static{
        SECKILL_SCRIPT = new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }
    //阻塞队列
    private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024*1024);

    //线程池
    private static final ExecutorService SECKILL_ORDER_EXCUTOR = Executors.newSingleThreadExecutor();

    //PostConstruct满足在大类启动后,里面执行init
    @PostConstruct
    private void init(){
        SECKILL_ORDER_EXCUTOR.submit(new VoucherOrderHandler());
    }
    //放在阻塞队列里面的?
    private class VoucherOrderHandler implements Runnable{
        @Override
        public void run() {
            try {
                VoucherOrder voucherOrder = orderTasks.take();
                handleVoucherOrder(voucherOrder);
            } catch (Exception e) {
                log.error("处理订单异常", e);
            }


        }
    }
    private void handleVoucherOrder(VoucherOrder voucherOrder) {
        Long userId = voucherOrder.getUserId();
        RLock lock = redissonClient.getLock("lock:order:" + userId);
        boolean isLock = lock.tryLock();
        if (!isLock){
            log.error("不允许重复下单!");
            return;
        }
        try{
            proxy.createVoucherOrder(voucherOrder);
        }finally {
            lock.unlock();
        }
    }
    private IVoucherOrderService proxy;
    @Override
    public Result seckillVoucher(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
        Long result = stringRedisTemplate.execute(
                SECKILL_SCRIPT,
                Collections.emptyList(),
                voucherId.toString(),
                userId.toString()
        );
        int r = result.intValue();
        if (r != 0){
            return Result.fail(r==1 ? "库存不足":"不能重复下单");
        }
        //返回值为0,证明可以下单。
        VoucherOrder voucherOrder = new VoucherOrder();
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        voucherOrder.setUserId(userId);
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);

        //阻塞队列,什么叫做阻塞队列?就是当有人向队列要元素的时候,队列为空,那么就等待,等待有元素才行
        orderTasks.add(voucherOrder);
        //获取代理对象,因为接下来子线程要处理阻塞队列的内容,所以子线程无法取得父线程的代理。
        proxy = (IVoucherOrderService) AopContext.currentProxy();

        return Result.ok(orderId);
    }
    @Transactional
    public void createVoucherOrder(VoucherOrder voucherOrder){

        Long userId = voucherOrder.getUserId();
        int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();
        if (count > 0) {
            log.error("用户已经购买过一次了!");
        }

        boolean success = seckillVoucherService.update()
                .setSql("stock = stock -1")
                .eq("voucher_id", voucherOrder.getVoucherId()).gt("stock", 0)
                .update();
        if (!success){
            log.error("库存不足");
        }
        save(voucherOrder);

    }
}

优缺点:
在这里插入图片描述

这一部分,自己的错误,主要集中在lua脚本上。之后的学习,真的不要再偷懒不跟着测试功能了。

消息队列

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值