• Redis之实现优惠券高并发秒杀下单


    Redis之实现优惠券高并发秒杀下单

    实现逻辑如图:
    在这里插入图片描述
    此方法高并发情况下会出现超库存的问题,可使用乐观锁解决,乐观锁使用场景是更新的时候
    一:解决乐观锁方法一:加版本号,如图所示:
    在这里插入图片描述
    二:使用CAS法解决乐观锁,使用自己的数据作为判断数据,相当于简化版的版本号
    在这里插入图片描述
    需求:实现同一个优惠券,一个用户只能下一单
    在这里插入图片描述
    集群服务发现会让synchronize锁失效,如图所示,每一个服务都有自己的jvm,所以每个用户的线程不一样
    在这里插入图片描述
    使用分布式锁结局锁失效问题:让多进程都可见锁监听器,并互斥
    在这里插入图片描述
    使用redis设置分布式锁和释放锁:
    在这里插入图片描述
    在这里插入图片描述

    代码实现:给每个用户id加synchronize锁,并使用事务Transaction保证操作成功
    (1)获取锁和释放锁处理类:

    package com.hmdp.utils;
    
    import cn.hutool.core.lang.UUID;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.data.redis.core.script.DefaultRedisScript;
    
    import java.util.Collections;
    import java.util.concurrent.TimeUnit;
    
    public class SimpleRedisLock implements ILock {
    
        private String name;
        private StringRedisTemplate stringRedisTemplate;
    
        public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
            this.name = name;
            this.stringRedisTemplate = stringRedisTemplate;
        }
    
        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) {
            // 获取线程标示
            String threadId = ID_PREFIX + 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());
        }
        /*@Override
        public void unlock() {
            // 获取线程标示
            String threadId = ID_PREFIX + Thread.currentThread().getId();
            // 获取锁中的标示
            String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
            // 判断标示是否一致
            if(threadId.equals(id)) {
                // 释放锁
                stringRedisTemplate.delete(KEY_PREFIX + name);
            }
        }*/
    }
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    lua语句:

    -- 比较线程标示与锁中的标示是否一致
    if(redis.call('get', KEYS[1]) ==  ARGV[1]) then
        -- 释放锁 del key
        return redis.call('del', KEYS[1])
    end
    return 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (2)实现redis的分布式锁

    @Transactional
        public Result createVoucherOrder(Long voucherId) {
            // 5.一人一单
            Long userId = UserHolder.getUser().getId();
    
            // 创建锁对象
            SimpleRedisLock redisLock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
            // 尝试获取锁
            boolean isLock = redisLock.tryLock(1200);
            // 判断
            if(!isLock){
                // 获取锁失败,直接返回失败或者重试
                return Result.fail("不允许重复下单!");
            }
    
            try {
                // 5.1.查询订单
                int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
                // 5.2.判断是否存在
                if (count > 0) {
                    // 用户已经购买过了
                    return Result.fail("用户已经购买过一次!");
                }
    
                // 6.扣减库存
                boolean success = seckillVoucherService.update()
                        .setSql("stock = stock - 1") // set stock = stock - 1
                        .eq("voucher_id", voucherId).gt("stock", 0) // where id = ? and stock > 0
                        .update();
                if (!success) {
                    // 扣减失败
                    return Result.fail("库存不足!");
                }
    
                // 7.创建订单
                VoucherOrder voucherOrder = new VoucherOrder();
                // 7.1.订单id
                long orderId = redisIdWorker.nextId("order");
                voucherOrder.setId(orderId);
                // 7.2.用户id
                voucherOrder.setUserId(userId);
                // 7.3.代金券id
                voucherOrder.setVoucherId(voucherId);
                save(voucherOrder);
    
                // 7.返回订单id
                return Result.ok(orderId);
            } finally {
                // 释放锁
                redisLock.unlock();
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
  • 相关阅读:
    elementui table超出两行显示...鼠标已入tip显示
    【go/方法记录】局部坐标与世界坐标间的相互转换(位置/方向)
    PTE阶段规划
    MySQL数据库所用到的八十八张表(七到十二)
    基于NodeJs+Express+Mysql学生社团活动管理系统
    【C++Primer---C++知识点记录*VI---泛型算法】
    异构数据源同步之数据同步 → datax 改造,有点意思
    支持向量机(SVM)—— 详细推导及案例应用可视化
    Jmeter接口测试参数化方法有哪些?怎么做看好了
    Web前端:面向程序员的五大前端技术
  • 原文地址:https://blog.csdn.net/hcyxsh/article/details/127721088