【redis】分布式锁

一、前言

      最近项目中,基本功能实现了,准备都访问多的接口加缓存。当然缓存就想到了redis。正好自己也查了查redis带来的一些问题:缓存穿透、缓存并发、热点缓存等。

      也想到自己负责的模块涉及到资金,同一时间只能有一个人操作,想象一下,同一时间2个用户同时还款放款,一个人账户增加一个减少,为了方式同时操作数据不一致,需要锁。如果是单体服务,可以直接利用数据库的行锁或者表锁。如果是微服务集群,多个客户端同时修改一个共享数据就需要分布式锁了。

二、使用redis实现分布式锁

      场景:秒杀

      现在有100个商品,进行秒杀,因为商品价格便宜,所以就会有很多人来抢。首先我们要保证的就是:

      1 商品最后不能成负数

      2 防止超出数据库连接池数目

      3 保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。

这里写图片描述

      业务核心:

      每个用户访问的时候,先要获取锁,如果获取到了,就可以购买商品,如果获取不到就再次访问。

  /**
     * 分布式锁尝试
     * 每个用户访问的时候,先要获取锁,如果获取到了,就可以购买商品,如果获取不到就再次访问
     *
     *
     * @param feeCode
     * @throws Exception
     */
    public void stock(String feeCode) throws Exception {
       //加锁
        long time = System.currentTimeMillis()+50000;
        if (!redisService.lock(feeCode, String.valueOf(time))){
            throw  new Exception("没有获取到分布式锁呀!!");
        }

        //1.查询商品库存。为0则买完了
        if (count<1){
            throw new Exception("商品抢购结束");
        }else {
            //下单
            count--;
            Thread.sleep(1000);
            log.info(String.valueOf(count));
        }

        //解锁
        redisService.unlock(feeCode, String.valueOf(time));
    }

      分布式锁,加锁:

      这里要注意的是,使用redis的SetNx来实现加锁。SetNx是如果key不存在就存储到redis中,并返回1,否则如果key存在,就什么也不做,返回0。

      这里呢,因为很多人抢一种产品,所以key可以为productTypeId ,value为 当前时间+超时时间。

      针对这个超时时间呢?大家在平常的时候很常见,就是比如在某宝、某东、12306等买东西的时候,当添加购物车后,前去结账,结账的时候,都会有一个请在30分钟内付款。否则就自动放弃。我们的这个超时时间就是类似付款时候的30分钟。这个是要根据我们具体的业务具体设定的。

      如果锁过期,获取上一个锁的时间,如果锁过期,也就是存储的锁的值小于当前时间,那么使用GetSet方法把值替换掉。

 /**
     * 分布式锁,加锁
     * @param key
     * @param value  当前时间+超时时间
     * @return
     */
    public boolean lock(String key, String value) {
        //如果key存在,就返回,否则就存储
        if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
            return true;
        }

        String currentValue = (String) redisTemplate.opsForValue().get(key);
        //如果锁过期
        if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
            //获取上一个锁的时间
            String oldValue = (String) redisTemplate.opsForValue().getAndSet(key, value);
            if (!StringUtils.isEmpty(oldValue)&&oldValue.equals(currentValue) ){
                return true;
            }
        }
        return false;
    }

      分布式锁,解锁:

      我们先来根据

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

    }

三、小结

      保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。分布式锁就可以实现这个功能。用到了redis的两个命令,SetNx和GetSet。了解一下。

猜你喜欢

转载自blog.csdn.net/kisscatforever/article/details/80902989