Reids实现分布式锁(秒杀功能实现,Synchronized与redis的对比)

传统Synchronized的使用

话不多说我们先来看代码:

SeckillController

@Controller
@RequestMapping("/Seckill")
public class SeckillController {

    @Autowired
    private SeckillService seckillService;


    /*
    * 秒杀功能
    * @param productId
    * @return
    * */
    @GetMapping("/do_kill")
    public String list(Model model, User user,
                       @RequestParam("productId")String productId) throws Exception {
        model.addAttribute("user", user);
        //判断用户是否登入
        if (user == null) {
            //返回登入页面
            return "login";
        }
        // 调用秒杀
        OrderMaster orderMaster = seckillService.querySecKillProductInfo(productId, user)

        if (orderMaster == null) {
            return "Seckill_fail";
        } else {
            return "Seckill_success";
        }

    }


}

然后就是核心的 SeckillServiceImpl

/**
 * @Author: WH
 * @Date: 2019/6/1 22:32
 * @Version 1.0
 */
public class SeckillServiceImpl {

    @Autowired
    private ProductService productService;
    @Autowired
    private OrderService orderService;

    @Transactional
    public synchronized OrderMaster querySecKillProductInfo(String productId, User user) {
        // 1. 判断商品库存
        ProductInfoVO productInfoVO = productService.getProductsVOByProductInfoVoId(productId);
        int stock = productInfoVO.getStockCount();
        // 2. 如果商品库存为0,秒杀失败
        if (stock <= 0) {
            throw  new SellException(100, "商品秒杀结束");
        }
            //  3. 判断是否重复秒杀
            OrderMaster orderMaster = orderService.findOne(user.getId(), productId);
            if (orderMaster != null) {
               throw new SellException(101, "商品不能重复秒杀");
            }
            // 4. 下单
        orderService.save(productId, user.getId());
            // 5. 减库存
        orderService.update(productId);
        //返回订单完成详细信息
        return orderMaster = orderService.findOne(user.getId(), productId);

    }
}

总结:
使用 synchronized 能实现功能,但是不能做到细粒度的控制
只适合单机,不适合分布式锁

使用redis实现单节点分布式锁:

需要注意的是下面的实现仅仅是单节点的Redis分布式锁实现,而且不是最佳实践仍有很多问题,不考虑多节点数据同步问题,如果需要实现集群版本的Redis分布式锁建议使用Redisson实现

实现分布式锁需要满足的四个条件:

  1. 互斥性。在任意时刻,只有一个客户端能持有锁。
  2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  3. 加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
  4. 具有容错性。只要大多数Redis节点正常运行,客户端就能够获取和释放锁(单节点无法保证)。

单节点Redis实现分布式锁主要靠setnx这个命令

我们来看看redis的这两个命令:
SETNX key value: 将key设置值为value,如果key不存在,这种情况下等同SET命令。 当key存在时,什么也不做。SETNX是”SET if Not eXists”的简写.

例子:

redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"
redis> 

GETSET key value: 自动将key对应到value并且返回原来key对应的value。如果key存在但是对应的value不是字符串,就返回错误
例子:

redis> INCR mycounter
(integer) 1
redis> GETSET mycounter "0"
"1"
redis> GET mycounter
"0"
redis> 

现在我们不使用 Synchronized 加锁,使用redis实现分布式锁

加锁解锁的核心逻辑

RedisLock

@Component
@Log4j
public class RedisLock {

    @Autowired
    private StringRedisTemplate redisTemplate;

    /*
    * 加锁
    * @param key
    * @param value 当前时间 + 超时时间
    * */
    public boolean lock(String key, String value) {
        //setex 相当于setIfAbsent 返回值为boolean。
        if (redisTemplate.opsForValue().setIfAbsent( key, value)) {
            return  true;
        }
            
            // 取出的currentValue = A  这两个线程的vlaue 都是B  只会是其中一个线程拿到锁
            String currentValue = redisTemplate.opsForValue().get(key);
            //如果锁过期
            if (!StringUtils.isEmpty(currentValue)
                    && Long.parseLong(currentValue) < System.currentTimeMillis()) {
                //获取上一个锁的时间,使用 getset命令
                String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
                if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
                    return true;
                }
            }

            return false;
        }

    //解锁
    public void unlock (String key, String value){
        try {

            String currentValue = redisTemplate.opsForValue().get(key);
            if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
                redisTemplate.opsForValue().getOperations().delete(key);
            }
        } catch (Exception e) {
            log.error("[redis分布式锁解锁异常]");
        }
        }

    }

这里的实现其实是有明显的问题的,这种方式肯定不是最佳实现,存在问题有如下:

  1. 单节点Reids如果挂掉导致整个服务获取不锁,使整个服务处于不可用状态
  2. 锁过期的时间设置多久合适,如果过短导致业务还没执行完成下一个线程又能获取到锁了,那如果过长呢?过长又会导致某个线程因异常未获取到锁导致其他线程长时间或不去不到锁。主要问题还是Redis不能自动释放锁。
  3. Redis分布式锁的最佳实践还是应该考入使用Redisson
  4. 不支持重入(可手动基于一个计数器来实现)

SeckillServiceImpl

/**
 * @Author: WH
 * @Date: 2019/6/1 22:32
 * @Version 1.0
 */
public class SeckillServiceImpl {

    private static final int  TIMEOUT = 10 * 1000;

    @Autowired
    RedisLock redisLock;

    @Autowired
    private ProductService productService;
    @Autowired
    private OrderService orderService;

    @Transactional
    public synchronized OrderMaster querySecKillProductInfo(String productId, User user) {

        //加锁
        long time = System.currentTimeMillis() + TIMEOUT;
        if (!redisLock.lock(productId, String.valueOf(time))) {
            throw new SellException(103, "获取redis锁失败");
        }

        // 1. 判断商品库存
        ProductInfoVO productInfoVO = productService.getProductsVOByProductInfoVoId(productId);
        int stock = productInfoVO.getStockCount();
        // 2. 如果商品库存为0,秒杀失败
        if (stock <= 0) {
            throw  new SellException(100, "商品秒杀结束");
            return  null;
        }
            //  3. 判断是否重复秒杀
            OrderMaster orderMaster = orderService.findOne(user.getId(), productId);
            if (orderMaster != null) {
               throw new SellException(101, "商品不能重复秒杀");
                return  null;
            }
            // 4. 下单
        orderService.save(productId, user.getId());
            // 5. 减库存
        orderService.update(productId);
        //返回订单完成详细信息
        return orderMaster = orderService.findOne(user.getId(), productId);

        //解锁
        redisLock.unlock(productId, String.valueOf(time));
    }
}

Redisson分布式锁最佳实践

Redisson解决的问题:

  1. 不可重入性问题
  2. Redis集群中主备切换导致加锁失败:为了保证 Redis 的可用性,一般采用主从方式部署。主从数据同步有异步和同步两种方式,Redis 将指令记录在本地内存 buffer 中,然后异步将 buffer 中的指令同步到从节点,从节点一边执行同步的指令流来达到和主节点一致的状态,一边向主节点反馈同步情况。在包含主从模式的集群部署方式中,当主节点挂掉时,从节点会取而代之,但客户端无明显感知。当客户端 A 成功加锁,指令还未同步,此时主节点挂掉,从节点提升为主节点,新的主节点没有锁的数据,当客户端 B 加锁时就会成功
  3. 集群脑裂:集群脑裂指因为网络问题,导致 Redis master 节点跟 slave 节点和 sentinel 集群处于不同的网络分区,因为 sentinel 集群无法感知到 master 的存在,所以将 slave 节点提升为 master 节点,此时存在两个不同的 master 节点。Redis Cluster 集群部署方式同理。当不同的客户端连接不同的 master 节点时,两个客户端可以同时拥有同一把锁

Redis作者 antirez基于分布式环境下提出了一种更高级的分布式锁的实现:Redlock算法

加锁步骤大致分为如下三步

  1. 客户端获取当前时间
  2. 客户端按顺序依次向N个Redis实例执行加锁操作

这里的加锁操作和在单实例上执行的加锁操作一样,使用 SET 命令,带上 NX,EX/PX 选项,以及带上客户端的唯一标识。当然,如果某个 Redis 实例发生故障了,为了保证在这种情况下,Redlock 算法能够继续运行,我们需要给加锁操作设置一个超时时间。
如果客户端在和一个 Redis 实例请求加锁时,一直到超时都没有成功,那么此时,客户端会和下一个 Redis 实例继续请求加锁。加锁操作的超时时间需要远远地小于锁的有效时间,一般也就是设置为几十毫秒。

  1. 一旦客户端完成了和所有Redis实例的加锁操作,客户端开始计算整个加锁的总耗时

客户端需要满足如下条件才能认为是加锁成功

  1. 客户端从超过半数(大于等于 N/2+1)的 Redis 实例上成功获取到了锁;
  2. 客户端获取锁的总耗时没有超过锁的有效时间

如果客户端在和所有实例执行完加锁操作后,没有满足这两个条件,客户端向所有Redis节点发起释放锁操作

深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值