redis分布式锁

两盒软妹~` 提交于 2019-12-05 07:18:49


 关于分布式锁的概念网上太多了,这里就不罗嗦了。对于开发者来说,最关心的应该是什么情况下使用分布式锁。

使用分布式锁,一般要满足以下几个条件:

· 分布式系统(关键是分布式)

· 共享资源(各系统访问同一资源,资源的载体可能是传统关系型数据库或者NoSQL)

· 同步访问(没有同步访问,谁管你资源竞争不竞争)

场景案例

· 光说不练假把式,上点干货。管理后台的部署架构(多台tomcat服务器+redis+mysql)就满足使用分布式锁的条件。多台服务器要访问redis全局缓存的资源,如果不使用分布式锁就会出现问题。 看如下伪代码:

long N=0L;
//N从redis获取值
if(N<5){
N++;
//N写回redis
}


· 从redis获取值N,对数值N进行边界检查,自加1,然后N写回redis中。 这种应用场景很常见,像秒杀,全局递增ID、IP访问限制等。以IP访问限制来说,恶意攻击者可能发起无限次访问,并发量比较大,分布式环境下对N的边界检查就不可靠,因为从redis读的N可能已经是脏数据。传统的加锁的做法(如java的synchronized和Lock)也没用,因为这是分布式环境,这个同步问题的救火队员也束手无策。在这危急存亡之秋,分布式锁终于有用武之地了。

所谓知己知彼,百战百胜。要想用好他,首先你要了解他。分布式锁可以基于很多种方式实现,比如zookeeper、redis...。不管哪种方式,他的基本原理是不变的:用一个状态值表示锁,对锁的占用和释放通过状态值来标识。

这里主要讲如何用redis实现分布式锁。

实现原理

Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。redis的SETNX命令可以方便的实现分布式锁。

SETNX命令(SET if Not eXists) 语法: SETNX key value 功能: 当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。 有了以上Redis知识了,写个简单的分布式锁就不是什么难事。

直接贴java代码!

public class RedisLock implements Lock {

    @Autowired

    protected StringRedisTemplate redisTemplate;

    private static final Logger logger = Logger.getLogger(RedisLock.class);

    // lock flag stored in redis
    private static final String LOCKED = "TRUE";

    // timeout(ms)
    private static final long TIME_OUT = 30000;

    // lock expire time(s)
    public static final int EXPIRE = 60;

    // private Jedis jedis;
    private String key;

    // state flag
    private volatile boolean locked = false;

    private static ConcurrentMap<String, RedisLock> map = Maps.newConcurrentMap();

    public RedisLock(String key) {
        this.key = "_LOCK_" + key;
        redisTemplate = (StringRedisTemplate) ApplicationContextHolder.getBean("redisTemplate");
    }

    public static RedisLock getInstance(String key) {
        return map.getOrDefault(key, new RedisLock(key));
    }

    public void lock(long timeout) {
        long nano = System.nanoTime();
        timeout *= 1000000;
        final Random r = new Random();
        try {
            while ((System.nanoTime() - nano) < timeout) {
                if (redisTemplate.getConnectionFactory().getConnection().setNX(key.getBytes(), LOCKED.getBytes())) {
                    redisTemplate.expire(key, EXPIRE, TimeUnit.SECONDS);
                    locked = true;
                    logger.debug("add RedisLock[" + key + "].");
                    break;
                }
                Thread.sleep(3, r.nextInt(500));
            }
        } catch (Exception e) {
        }
    }

    @Override
    public void unlock() {
        if (locked) {
            logger.debug("release RedisLock[" + key + "].");
            redisTemplate.delete(key);
        }
    }

    @Override
    public void lock() {
        lock(TIME_OUT);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public Condition newCondition() {
        return null;
    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }
}

基准测试

饭已OK,快来MIXI啦。

~~~等等,还得让评审来评价下这饭做得好不好吃。

写一段测试代码来对比测试下。

public void concurrentTest() {
        final Printer outer = new Printer();
        new Thread(new Runnable() {
            @Override
            public void run() {
                outer.output("I am a boy.");
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                outer.output("You are a girl.");
            }
        }).start();
    }
?
不加锁的Printer:
?
class Printer {
      public void output(String name) {

          for (int i = 0; i < name.length(); i++) {
              System.out.print(name.charAt(i));
              try {
                  Thread.sleep(10);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
  }
?
?使用java内置的锁ReentrantLock:
Lock lock=new ReentrantLock();class Printer {
      public void output(String name) {
          lock.lock();
          for (int i = 0; i < name.length(); i++) {
              System.out.print(name.charAt(i));
              try {
                  Thread.sleep(10);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
          lock.unlock();
      }
  }
?
?使用分布式锁RedisLock:
Lock lock=new ReentrantLock();class Printer {
      public void output(String name) {
         Lock lock = new RedisLock("lock1");
          lock.lock();
          for (int i = 0; i < name.length(); i++) {
              System.out.print(name.charAt(i));
              try {
                  Thread.sleep(10);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
          lock.unlock();
      }
  }

对比测试结果如下:

项目

不加锁

java内置锁ReentrantLock

加分布式锁RedisLock

测试结果

IY oau m aar eb oa yg.irl.

I am a boy.You are a girl.

You are a girl.I am a boy.

写在后面

话说RedisLock能够正常使用了,也达到了预期效果。是不是这个分布式锁就万无一失呢?

这是一个悲观锁,RedisLock会不断尝试去获取锁,直到超时。也就是说,如果长时间获取不到,就会获取锁失败,相当于没加锁!具体的超时时间设置为多长,有待后期验证,再做优化。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!