基于redis的分布式锁

基于redis的分布式锁:
  大概原理:跟基于DB的类似,基于redis的分布式锁是争抢redis上的一个键,谁给这个键赋值了谁就抢到了锁,主要是用了redis的setnx机制,也就是set if not exists。实际一般不用setnx这个api,主要是粒度略粗,不能指定失效时间,用set(key,value,"nx","px",1000),几个参数分别为键、值、设置方式、时间单位、时间。
---------Talk Is Cheap, Just Show Me The Code-------------------------------------------------
获取锁:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * 阻塞锁
 * @return
 */
public boolean lock(){
    boolean flag = tryLock();
    if(flag){
        return true;
    }else{
        int time = (int) (Math.random() * 1000);
        try {//随机睡眠一段时间,避免多个线程同时睡眠,同时醒来
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return lock();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * 非阻塞锁
 * @return
 */
public boolean tryLock(){
    String str = UUID.randomUUID().toString();
    Jedis jedis = new Jedis("localhost");
    String result = jedis.set(lock,str,"NX","PX",1000);
    if(result!=null && result.equals("OK")){
        local.set(str);
        return true;
    }
    return false;
}
释放锁:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
   * 释放锁
   * @return
   */
  public void unlock(){
      String script =
              "if redis.call(\"get\",KEYS[1]) == ARGV[1] then " +
              "    return redis.call(\"del\",KEYS[1]) " +
              "else " +
              "    return 0 " +
              "end";
 
      ArrayList<String> keysList = new ArrayList<>();
      keysList.add(lock);
 
      ArrayList<String> argsList = new ArrayList<>();
      String args = local.get();
      argsList.add(args);
 
      Jedis jedis = new Jedis("localhost");
      jedis.eval(script, keysList, argsList);
  }
  解锁过程就是删除redis中数据的过程,由于有失效时间的问题,如果已经失效了就不用再删了,没失效则删除。 

  注意,这里用了一段lua脚本,原因是要保证解锁操作为原子操作,如果用普通jedis的api,要经过get值,比较,del值三个阶段,该过程中可能键已失效,导致误删。redis支持lua脚本,此处我们将该过程写成脚本,发到redis端,因为redis为单进程单线程,执行过程中可以保证该过程的原子性。

测试代码:

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
@Test
   public void testLock(){
       for(int i=0;i<10;i++){
           MyThread t = new MyThread("mythread"+i);
           t.start();
           cdl.countDown();
       }
       try {
           Thread.sleep(10000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
    
   //内部类
   class MyThread extends Thread{
       private String threadName;
       public MyThread(){};
       public MyThread(String threadName){
           this.threadName = threadName;
       }
       @Override
       public void run() {
           try {
               cdl.await();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           if(redisLock.lock()){
               System.out.println("线程-"+threadName+"获得锁,orderId为:"+MyResources.getInstance().getNextId());
               redisLock.unlock();
           }
 
       }
   }
执行结果:
线程-mythread3获得锁,orderId为:1
线程-mythread6获得锁,orderId为:2
线程-mythread5获得锁,orderId为:3
线程-mythread1获得锁,orderId为:4
线程-mythread2获得锁,orderId为:5
线程-mythread0获得锁,orderId为:6
线程-mythread7获得锁,orderId为:7
线程-mythread8获得锁,orderId为:8
线程-mythread9获得锁,orderId为:9
线程-mythread4获得锁,orderId为:10
------------------------------------------------
结果就不解释了,redis的分布式锁相比数据库的,一大优势是性能高,但没有缺点么?显然也不是:
1、锁的失效时间难以把握,过短业务没处理完就失效了,造成多个业务竞争资源,可能会失去锁应有的作用,过长会导致其它锁盲目等待,导致锁的性能较低,经验来看,一般为单线程处理业务时长的2-3倍较合适;
2、可能出现锁失效的问题,比如某个线程执行时间特别长,超出了有效期,别的线程也会抢到锁,可能造成锁失效;
3、在redis集群中不太适合,因为是基于master节点的,master节点挂掉的时候如果数据没有同步到salve中,那么会造成锁失效;
整体来说,redis的分布式锁仍然有较多问题,下一篇我们整理基于zk的分布式锁,并对三种锁作比较。

 
 

posted @   facelessvoidwang  阅读(264)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示