Redis lock concept and implementation of a distributed lock (turn)

 

Recently, the issue was widely distributed mentioned, such as distributed transactions, a distributed framework, ZooKeeper, SpringCloud and so on. This paper first review the concept lock, and then introduce distributed lock, and how to use Redis to implement a distributed lock.

First, the basic understanding of locks

First, we look at the concept of work and study in the lock.

Why did you speak distributed lock lock repeat it?

We all know, lock role is to solve the problem of multi-threaded thread-safe access to shared resources generated, and in the case of normal life is not much used in the lock, the lock may be some friends and some basic concepts use is not very clear, so we look at the lock, and then distributed lock-depth introduction.

Selling tickets through a small case of view, such as we try to steal dota2 ti9 tickets, if unlocked, then what would be the problem? At this time code is as follows:

Copy the code
the Thread Package; 

Import java.util.concurrent.TimeUnit; 

public class Ticket { 

    / ** 
     * Initial stocks 
     * * / 
    Integer = ticketNum. 8; 

    public void the reduce (int NUM) { 
        // determines whether sufficient inventory 
        if ((ticketNum - NUM)> = 0) { 
            the try { 
                TimeUnit.MILLISECONDS.sleep (200 is); 
            } the catch (InterruptedException E) { 
                e.printStackTrace (); 
            } 
            ticketNum - = NUM; 
            System.out.println (Thread.currentThread (). getName () + "sold successfully" 
            + NUM + "Zhang, remaining" + ticketNum + "tickets"); 
        } the else { 
            . System.err.println (Thread.currentThread () getName () + "It did not sell. "
                    + Num + "Zhang, remaining" + ticketNum + "tickets"); 
        } 
    } 

    public static void main (String [] args) {throws InterruptedException 
        Ticket Ticket Ticket = new new (); 
        // open grab votes threads 10, should supposedly two grab votes 
        for (int I = 0; I <10; I ++) { 
            new new the Thread (() ->. ticket.reduce (. 1), "user" + (i + 1)) start (); 
        } 
        the Thread.sleep (1000L); 
    } 

}
Copy the code

 

Code Analysis : There are eight ti9 tickets, set up 10 threads (ie analog 10 people) to concurrent grab votes, if successful show success grab, grab it fails to display failure. It stands to reason there should be 8 people grab a success, two people are competing for the failure, the following look at the results:

We found inconsistencies and expected operating results, the 10 people actually have to buy a ticket, that there has been a thread safety problem, then what causes it?

The reason is that multiple threads created between the time difference .

As shown, only a ticket, but the ticket both threads read margin is 1, that is to say not wait until thread B thread A change inventory has succeeded to grab votes.

How to solve it? Surely we all know, add synchronized keyword on it, when to reduce method in a thread, other threads are blocked waiting in the queue, so that multiple threads to shared variables competition issues will not occur.

for example

For example, we went to the gym, if a lot of people at the same time with a single machine, while running on a treadmill, and a big problem occurs, we will battle it. If we add a lock on the gym door, only to get the lock key personnel can go exercise, other people waiting at the door, so you can avoid all competition for fitness equipment. code show as below:

Copy the code
the reduce the synchronized void public (int NUM) { 
        // determines whether enough stock 
        IF ((ticketNum - NUM)> = 0) { 
            the try { 
                TimeUnit.MILLISECONDS.sleep (200 is); 
            } the catch (InterruptedException E) { 
                e.printStackTrace ( ); 
            } 
            ticketNum - = NUM; 
            System.out.println (Thread.currentThread () getName () + "sold successfully." 
            + NUM + "Zhang, remaining" + ticketNum + "tickets"); 
        } {the else 
            the System .err.println (. Thread.currentThread () getName ( ) + " not sold" 
                    + NUM + "Zhang, remaining" + ticketNum + "tickets"); 
        } 
    }
Copy the code

 

operation result:

Sure, resulting in two people do not succeed to grab votes, it seems that our goal in reaching.

Second, lock performance optimization

2.1 shorten the holding time locks

In fact, according to our understanding of everyday life, it is impossible that only one person in the entire gym sports. So we only need a machine to lock on it, such as a person running, another person can do other sports.

For ticketing system, we only need to modify the operation of the code inventory lock on it, other codes can still be carried out in parallel, it would greatly reduce the time to hold the lock code is modified as follows:

Copy the code
void reduceByLock public (int NUM) { 
        Boolean = In Flag to false; 

        the synchronized (ticketNum) { 
            IF ((ticketNum - NUM)> = 0) { 
                ticketNum - = NUM; 
                In Flag = to true; 
            } 
        } 
        IF (In Flag) { 
            the System.out. println (. Thread.currentThread () getName ( ) + " sold successfully" 
                        + NUM + "Zhang, remaining" + ticketNum + "tickets"); 
        } 
        the else { 
            . System.err.println (Thread.currentThread () getName () + "not sold" 
                        + NUM + "Zhang, remaining" + ticketNum + "tickets"); 
        } 
        IF (ticketNum == 0) {
            System.out.println ( "Processed" + (System.currentTimeMillis () - startTime ) + " msec"); 
        } 
    }
Copy the code

 

The aim is to make full use of cpu resources, improve the efficiency of the code .

Here we make a print time in two ways:

Copy the code
the reduce the synchronized void public (int NUM) { 
        // determines whether enough stock 
        IF ((ticketNum - NUM)> = 0) { 
            the try { 
                TimeUnit.MILLISECONDS.sleep (200 is); 
            } the catch (InterruptedException E) { 
                e.printStackTrace ( ); 
            } 
            ticketNum - = NUM; 
            IF (ticketNum == 0) { 
                System.out.println ( "Processed" + (System.currentTimeMillis () - startTime ) + " msec"); 
            } 
            System.out.println (the Thread . .currentThread () getName () + " sold successfully" 
            + NUM + "Zhang, remaining" + ticketNum + "tickets"); 
        } the else { 
            System.err.println (Thread.currentThread ().getName () + "did not sell."
                    + Num + "Zhang, remaining" + ticketNum + "tickets"); + TicketNum + "tickets"); 
        }
    }
Copy the code

 

Sure enough, the only part of the code lock will provide greatly the efficiency of the code.

So, to solve thread safety issues, we have to take into account the efficiency of code execution after locking .

2.2 reducing lock granularity

For example, there are two movies, which are recently released Damned Rebels and Spider-Man, we simulate the process of buying a payment, so wait method, plus a CountDownLatch await method, the results are as follows:

Copy the code
the Thread Package; 

Import java.util.concurrent.CountDownLatch; 

public class Movie { 
    Private Final LATCH a CountDownLatch a CountDownLatch new new = (. 1); 
    // Damned Zha 
    Private Integer = 20 is babyTickets; 

    // Spider 
    Private spiderTickets Integer = 100; 

    public void showBabyTickets the synchronized () throws InterruptedException { 
        System.out.println ( "magic remaining child Zha votes is:" + babyTickets); 
        // for later 
        latch.await (); 
    } 

    public void showSpiderTickets the synchronized () throws InterruptedException { 
        the System. out.println ( "Spider remaining votes is:" + spiderTickets); 
        // later 
    } 

    public static void main (String [] args) { 
        Movie Movie Movie new new = ();
        new Thread(() -> {
            try {
                movie.showBabyTickets();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        },"用户A").start();

        new Thread(() -> {
            try {
                movie.showSpiderTickets();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        },"用户B").start();
    }

}
Copy the code

 

Results of the:

Zha magic remaining child votes: 20

 

We found that the time to buy tickets Rebels blocked affects buy Spiderman tickets, in fact, are independent of each other between the two movies, so we need to reduce the size of the lock , the lock movie the whole object becomes two lock global variables, modify the code as follows:

Copy the code
void showBabyTickets public () {throws InterruptedException 
        the synchronized (babyTickets) { 
            System.out.println ( "magic remaining child Zha votes is:" + babyTickets); 
            // for later 
            latch.await (); 
        } 
    } 

    public void showSpiderTickets () InterruptedException {throws 
        the synchronized (spiderTickets) { 
            System.out.println ( "Spider remaining votes is:" + spiderTickets); 
            // later 
        } 
    }
Copy the code

 

Results of the:

Zha magic remaining child votes: 20 
remaining votes as Spider: 100

 

Now two movie tickets do not affect each other, and this is the second lock to optimize the way: reducing lock granularity . Incidentally, Java Concurrency bag ConcurrentHashMap put a big lock is turned into 16 small lock, the lock through segmented approach to achieve efficient concurrent security.

2.3 Lock isolated

Lock separation is often said that separate read and write, we lock into a read and write locks, read locks do not need to clog, and write locks to consider concurrency issues.

Third, the type of lock

  • Fair locks: ReentrantLock
  • Unfair lock: Synchronized, ReentrantLock, cas
  • Pessimistic locking: Synchronized
  • Optimistic locking: cas
  • Exclusive lock: Synchronized, ReentrantLock
  • Shared locks: Semaphore

Here is not about the concept of each lock, we can study by themselves, lock can also be classified according biased lock, lock lightweight, heavyweight lock.

Four, Redis distributed lock

Understand the basic concepts of locks and lock after optimization, focusing on the concept of distributed lock.

As shown in the figure is we built a distributed environment, there are three ticket items, one for inventory, each system will have multiple threads, and the same as above, modifying the operation of the stock plus the lock can not guarantee this 6 thread-safe threads of it?

Of course not, because each has its own ticketing system JVM process, independent of each other, so add synchronized thread can only guarantee the safety of a system, does not guarantee a distributed thread-safe.

So it is necessary for the three systems are common middleware to solve this problem.

Here we choose Redis as a distributed lock , multiple systems with a Redis key in the set, only when there is no key in order to set up, and the key of which will correspond to uniquely identify a system when the system is accessing resources end after the key is deleted, achieve the purpose of releasing the lock.

Distributed Lock 4.1 points need to pay attention to what

1) mutually exclusive

At any time only one client can acquire the lock.

This is easy to understand, all systems can be only one holding the lock system.

2) anti-lock

If a client holding the lock when the collapse, did not release the lock, then another client can not get a lock, it will result in a deadlock, so to ensure that the client will release the lock.

Redis, we can set an expiration time lock to ensure that deadlock does not occur.

3) person who lock unlocked

The trouble should end it, locking and unlocking a client must be the same, plus a thread lock A client must be threaded client A to unlock the client can not be solved other client locks.

4) reentrant

When a client gets an object lock, the client can get a lock on the object again.

4.2 Redis Distributed Lock process

Redis distributed lock specific processes:

1) Firstly, the nature of Redis cache setting a key-value in the form of key-value pairs in Redis in, key is the name of the lock, then multiple threads clients to compete lock, the success of the competition, then the value is set to uniquely identify the client.

2) competition to lock the client to do two things:

  • The purpose of the effective time to set the lock is anti-lock  (critical)

Need based on business needs, and constantly stress test to determine the length of validity.

  • Assigned to uniquely identify the client, the purpose is to ensure that people who lock to unlock (very important)

So here the value is set to a unique identifier (such uuid).

3) access to shared resources

4) releases the lock, the lock is released in two ways, the first one is automatically released after the expiration of the lock , the second is to determine whether they have the authority to release the lock based on a unique identifier, if the identification is correct release the lock .

4.3 locking and unlocking

4.3.1 Locking

1) setnx lock command

set if not exists we will use Redis commands setnx, in the case lock setnx meaning that only the absence of settings will be successful.

2) the effective time setting locks to prevent deadlock expire

Lock requires two steps, think about what problem?

After locking up a client if we suddenly hang of it? Then the lock will not become a valid lock, then they may deadlock. Although the probability of this happening is very small, but if there are problems will be very serious, so we have put together two steps into one step.

Fortunately, Redis3.0 has put together two instructions to become a new command.

Official documents look jedis in the source code:

    public String set(String key, String value, String nxxx, String expx, long time) {
        this.checkIsInMultiOrPipeline();
        this.client.set(key, value, nxxx, expx, time);
        return this.client.getStatusCodeReply();
    }

 

This is what we want!

4.3.2 Unlock

  • Check whether you hold the lock (unique identification judgment);
  • Remove the lock.

Unlock is a two-step, the same should also ensure atomicity unlocked, the two steps combined into one step.

This is not possible with Redis, and can only rely on Lua script to achieve.

if Redis.call("get",key==argv[1])then
    return Redis.call("del",key)
else return 0 end

 

This is the period of Lua script to determine whether to hold its own lock and release the lock.

Why Lua script is atomicity it? Because Lua script is jedis use eval (), if the execution will complete the implementation of all the functions performed.

Five, Redis distributed lock code implementation

Copy the code
Lock class RedisDistributedLock the implements {public 

    // context, save the current lock holders ID 
    Private the ThreadLocal <String> = new new lockContext the ThreadLocal <String> (); 

    // default lock timeout 
    Private Long Time = 100; 

    // re of the 
    Private the Thread ownerThread; 

    public RedisDistributedLock () { 
    } 

    public void Lock () { 
        the while (! tryLock ()) { 
            the try { 
                the Thread.sleep (100); 
            } the catch (InterruptedException E) { 
                e.printStackTrace (); 
            } 
        } 
    } 


    public Boolean tryLock () { 
        return tryLock (Time, TimeUnit.MILLISECONDS); 
    }


    Boolean tryLock public (Long Time, TimeUnit Unit) { 
        String ID = UUID.randomUUID () toString ();. // each of the locking holder is assigned a unique ID 
        the Thread Thread.currentThread = T (); 
        Jedis jedis Jedis new new = ( "127.0.0.1", 6379); 
        // only when there is no lock latch and set lock valid time 
        if ( "OK" .equals (jedis.set ( "lock", id, "NX" , "PX", unit.toMillis (Time)))) { 
            // person holding the lock of the above mentioned id   
            lockContext.set (the above mentioned id); ① 
            // record the current thread 
            setOwnerThread (t); ② 
            return to true; 
        } IF the else (ownerThread == T) { 
            // because the lock is reentrant, it has been necessary to determine the current thread holds the lock 
            return to true; 
        } the else { 
            return to false;
        }
    }

    private void setOwnerThread(Thread t){
        this.ownerThread = t;
    }

    public void unlock() {
        String script = null;
        try{
            Jedis jedis = new Jedis("127.0.0.1",6379);
            script = inputStream2String(getClass().getResourceAsStream("/Redis.Lua"));
            if(lockContext.get()==null){
                //没有人持有锁
                return;
            }
            //删除锁  ③
            jedis.eval(script, Arrays.asList("lock"), Arrays.asList(lockContext.get()));
            lockContext.remove();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 将InputStream转化成String
     * @param is
     * @return
     * @throws IOException
     */
    public String inputStream2String(InputStream is) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int i = -1;
        while ((i = is.read()) != -1) {
            baos.write(i);
        }
        return baos.toString();
    }

    public void lockInterruptibly() throws InterruptedException {

    }

    public Condition newCondition() {
        return null;
    }
}
Copy the code

 

  • Context with a global variable to keep track of who's uuid holding a lock, unlock, when the need uuid Lua script as a parameter to determine whether to unlock.
  • To record the current thread to achieve reentrancy distributed lock if the current thread holds the lock, it also belongs to the lock success.
  • With the eval function to execute Lua script to ensure atomicity when unlocked.

Sixth, the distributed lock contrast

6.1 Distributed Database lock

1) implementation

Acquire a lock when inserted into the data, delete data when unlocked.

2) disadvantages

  • If you hang up the database will lead to business systems unavailable.
  • Unable to set the expiration time, will result in a deadlock.

6.2 based on distributed lock the zookeeper

1) implementation

Create a new node is locked in the directory specified node, when the lock is released to delete this temporary node. Because of the presence of a heartbeat is detected, the deadlock does not occur, more secure .

2) disadvantages

Performance in general, there is no efficient Redis.

and so:

  • From a performance point: the Redis> ZooKeeper> Database 
  • From a reliability (safety) of the angle: ZooKeeper> the Redis> Database 

Seven summary

Starting from the basic concept of locked proposed multithreading thread-safety issues will be access to shared resources, and then to solve the problem by locking thread-safe manner, this method performance will decline, need to: shortening the lock hold time reduced lock granularity, the lock latch to optimize separation of three ways.

After introducing the distributed lock of four characteristics:

  • Mutually exclusive
  • Anti-lock
  • Unlock locker
  • Reentrancy

Then Redis implements a distributed lock, lock, when used in the Redis commands to lock, unlock, when the means of the Lua script to ensure atomicity.

Finally, comparing the advantages and disadvantages of three distributed lock and usage scenarios.

Guess you like

Origin www.cnblogs.com/sunshijia1993/p/11590645.html