基于商品显示秒杀-一人一单业务_xzm_的博客-CSDN博客改进

分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁
分布式锁的五个基本要求:多进程可见,互斥,高可用,高性能,安全性
三种实现方式

redis

1.创建获取锁删除锁的工具类
public interface ILock {
    /**
     * 获取锁
     * @param timeoutSec 自动超时时间
     * @return
     */
    boolean tryLock(long timeoutSec);
    /**
     * 释放锁
     */
    void unlock();
}
package com.hmdp.utils;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;
public class SimpleRedisLock implements ILock{
    public String name;
    public StringRedisTemplate stringRedisTemplate;
    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }
    private static final String KEY_PREFIX="lock:";
    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程标识
        long thread = Thread.currentThread().getId();
        //获取锁
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, thread + "", timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(aBoolean);
    }
    @Override
    public void unlock() {
        //释放锁
        stringRedisTemplate.delete(KEY_PREFIX+name);
    }
}
2.修改代码
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
   @Resource
    private ISeckillVoucherService iSeckillVoucherService;
   @Resource
   private RedisIdWorker redisIdWorker;
   @Autowired
   private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result seckillVoucher(Long voucherId) {
        //查询优惠卷
        SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);
        //判断秒杀是否开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            //秒杀尚未开始
            return Result.fail("秒杀尚未开始,请耐心等待");
        }
        //判断秒杀是否结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            //秒杀已经结束
            return Result.fail("本次秒杀已经结束");
        }
        //秒杀处于正常时间段
        //判断库存是否充足
        if (voucher.getStock()<1) {
            //库存不足
            return Result.fail("本次秒杀已经被抢完");
        }
        //库存充足
        //从拦截器中获取用户id
        Long userId = UserHolder.getUser().getId();
        //对相同的id进行加锁
//        synchronized(userId.toString().intern()) {
            //获取代理对象
//            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            //在service中添加createVoucherOrder方法,也可利用idea代码修复功能自动添加
//            return proxy.createVoucherOrder(voucherId);
//            return createVoucherOrder(voucherId);
//        }
        //创建锁对象
        SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
        //获取锁
        boolean tryLock = lock.tryLock(500);
        if (!tryLock) {
            //获取锁失败
            return Result.fail("每人仅限一单");
        }
        //获取锁成功
        try {
            return createVoucherOrder(voucherId);
        } finally {
            //释放锁
            lock.unlock();
        }
    }
    @Transactional
    public  Result createVoucherOrder(Long voucherId) {
        //从拦截器中获取用户id
        Long userId = UserHolder.getUser().getId();
         /**
          * 进行判断,一人一单
          */
         int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
         if (count > 0) {
             //用户已经购买过商品
             return Result.fail("每位用户仅限购一单");
         }
         //扣减库存
         boolean update = iSeckillVoucherService.update().setSql("stock = stock - 1")
                 .eq("voucher_id", voucherId)
                 .gt("stock", 0)
                 .update();
         //判断库存扣减是否成功
         if (!update) {
             //库存扣减失败,返回信息
             return Result.fail("库存不足");
         }
         //库存扣减成功,添加订单信息
         VoucherOrder voucherOrder = new VoucherOrder();
         //添加订单id
         //使用订单生成器生成id
         long id = redisIdWorker.nextId("order");
         voucherOrder.setId(id);
         //添加用户id
         voucherOrder.setUserId(userId);
         //添加消费卷id
         voucherOrder.setVoucherId(voucherId);
         //添加到数据库
         boolean save = this.save(voucherOrder);
         if (!save) {
             //添加失败
             return Result.fail("下单失败");
         }
         return Result.ok(id);
    }
}
测试结果:实现了多个服务器下的一人一单
现阶段存在问题:当线程阻塞时间超过setnx的自动过期时间时可能导致一人多单和setnx的key误删情况

优化误删问题
思路:

实现:
修改工具类
package com.hmdp.utils;
import cn.hutool.core.lang.UUID;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;
public class SimpleRedisLock implements ILock{
    public String name;
    public 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.fastUUID().toString(true)+"-";
    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程标识
        String thread = ID_PREFIX+Thread.currentThread().getId();
        //获取锁
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, thread , timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(aBoolean);
    }
    @Override
    public void unlock() {
        //获取锁标识
        String thread = ID_PREFIX+Thread.currentThread().getId();
        //获取redis中的值
        String s = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
        if (thread.equals(s)){
            //释放锁
            stringRedisTemplate.delete(KEY_PREFIX+name);
        }
    }
}
现阶段依旧存在误删除的问题
逻辑:
解决方法:让判断锁标识与释放锁保持原子性
Lua脚本

解决方法
1.创建nulock.lua文件
2.编写lua脚本
-- 获取锁中的线程标识 get key
local  id=redis.call('get',KEYS[1])
--比较线程标识与锁中的标识是否一致
if id == ARGV[1] then
    --释放锁
    return redis.call('del',KEYS[1])
end
return 03.修改unlock方法
 //创建接收lua脚本
    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
    //在静态方法中初始化UNLOCK_SCRIPT
    static {
        UNLOCK_SCRIPT=new DefaultRedisScript<>();
        //设置接收lua脚本文件
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("nulock.lua"));
        //设置返回值类型
        UNLOCK_SCRIPT.setResultType(Long.class);
    }
    @Override
    public void unlock() {
        //使用lua脚本
        stringRedisTemplate.execute(UNLOCK_SCRIPT,
                Collections.singletonList(KEY_PREFIX + name),
                ID_PREFIX+Thread.currentThread().getId()
                );
    }到目前为止已经可以做到生产可用
redis分布式锁的优化
需要优化的问题:不可重入,不可重试,超时释放,主从一致

解决方法:使用redis的框架redisson实现分布式锁

使用方法:
1.引入依赖
<!--        redis框架redisson-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.6</version>
        </dependency>2.编写配置文件
@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient(){
        //配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.2.182:6379").setPassword("123456");
        //创建Redissonclient对象
        return Redisson.create(config);
    }
}
3.修改业务类(仅进行注入了RedissonClient 和创建锁对象和trylock的参数)
package com.hmdp.service.impl;
import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.Voucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.service.IVoucherService;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.SimpleRedisLock;
import com.hmdp.utils.UserHolder;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
   @Resource
    private ISeckillVoucherService iSeckillVoucherService;
   @Resource
   private RedisIdWorker redisIdWorker;
   @Autowired
   private StringRedisTemplate stringRedisTemplate;
    @Autowired
   private RedissonClient redissonClient;
    @Override
    public Result seckillVoucher(Long voucherId) {
        //查询优惠卷
        SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);
        //判断秒杀是否开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            //秒杀尚未开始
            return Result.fail("秒杀尚未开始,请耐心等待");
        }
        //判断秒杀是否结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            //秒杀已经结束
            return Result.fail("本次秒杀已经结束");
        }
        //秒杀处于正常时间段
        //判断库存是否充足
        if (voucher.getStock()<1) {
            //库存不足
            return Result.fail("本次秒杀已经被抢完");
        }
        //库存充足
        //从拦截器中获取用户id
        Long userId = UserHolder.getUser().getId();
        //对相同的id进行加锁
//        synchronized(userId.toString().intern()) {
            //获取代理对象
//            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            //在service中添加createVoucherOrder方法,也可利用idea代码修复功能自动添加
//            return proxy.createVoucherOrder(voucherId);
//            return createVoucherOrder(voucherId);
//        }
        //创建锁对象
//        SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
        RLock lock = redissonClient.getLock("lock:order:" + userId);
        //获取锁
        boolean tryLock = lock.tryLock();
        if (!tryLock) {
            //获取锁失败
            return Result.fail("每人仅限一单");
        }
        //获取锁成功
        try {
            return createVoucherOrder(voucherId);
        } finally {
            //释放锁
            lock.unlock();
        }
    }
    @Transactional
    public  Result createVoucherOrder(Long voucherId) {
        //从拦截器中获取用户id
        Long userId = UserHolder.getUser().getId();
         /**
          * 进行判断,一人一单
          */
         int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
         if (count > 0) {
             //用户已经购买过商品
             return Result.fail("每位用户仅限购一单");
         }
         //扣减库存
         boolean update = iSeckillVoucherService.update().setSql("stock = stock - 1")
                 .eq("voucher_id", voucherId)
                 .gt("stock", 0)
                 .update();
         //判断库存扣减是否成功
         if (!update) {
             //库存扣减失败,返回信息
             return Result.fail("库存不足");
         }
         //库存扣减成功,添加订单信息
         VoucherOrder voucherOrder = new VoucherOrder();
         //添加订单id
         //使用订单生成器生成id
         long id = redisIdWorker.nextId("order");
         voucherOrder.setId(id);
         //添加用户id
         voucherOrder.setUserId(userId);
         //添加消费卷id
         voucherOrder.setVoucherId(voucherId);
         //添加到数据库
         boolean save = this.save(voucherOrder);
         if (!save) {
             //添加失败
             return Result.fail("下单失败");
         }
         return Result.ok(id);
    }
}
原理:











![【群智能算法改进】一种改进的沙丘猫群优化算法 改进沙丘猫群算法 改进SCSO[1]【Matlab代码#34】](https://img-blog.csdnimg.cn/e95879a529e04ce796e80c5913678e4d.png#pic_center)








